diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 70% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug_report.md index 0d9dead258039..38ca8d8d47a4e 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,3 +1,12 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + ### Expected Behavior - - + ### Current Behavior - - + ### Context -### Steps to Reproduce (for bugs) +### Steps to Reproduce ### Your Environment - - * Build scan URL: + +Build scan URL: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000000000..c9f03625b66d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,28 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + + + + + +### Expected Behavior + + +### Current Behavior + + +### Context + + diff --git a/.github/ISSUE_TEMPLATE/regression.md b/.github/ISSUE_TEMPLATE/regression.md new file mode 100644 index 0000000000000..9cb1fa40e500b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/regression.md @@ -0,0 +1,34 @@ +--- +name: Regression +about: Report a problem about something that used to work +title: '' +labels: a:regression +assignees: '' + +--- + + + + + +### Expected Behavior + + +### Current Behavior + + +### Context + + + + +### Steps to Reproduce + + + +### Your Environment + + +Build scan URL: diff --git a/.github/SUPPORT.md b/.github/SUPPORT.md new file mode 100644 index 0000000000000..1f89f0370daa3 --- /dev/null +++ b/.github/SUPPORT.md @@ -0,0 +1,9 @@ +# Getting Help with Gradle + +You can search for [help](https://gradle.org/help/) across our website, forums and StackOverflow. + +You can attend one of our [FREE online classes](https://gradle.org/training/). + +You can subscribe to our [monthly newsletter](https://newsletter.gradle.com/). + +We have lots of [other resources](https://gradle.org/resources/) to help you learn more about Gradle. diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 7d467a0fdf1ad..b1223ec682481 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -32,7 +32,7 @@ diff --git a/.idea/inspectionProfiles/Gradle.xml b/.idea/inspectionProfiles/Gradle.xml index a554a4f25d399..3d0904d5fcbb4 100644 --- a/.idea/inspectionProfiles/Gradle.xml +++ b/.idea/inspectionProfiles/Gradle.xml @@ -20,6 +20,9 @@ + + + diff --git a/.teamcity/Gradle_AgentTest/patches/projects/Gradle_AgentTest.kts b/.teamcity/Gradle_AgentTest/patches/projects/Gradle_AgentTest.kts deleted file mode 100644 index 72abdd8ad1de6..0000000000000 --- a/.teamcity/Gradle_AgentTest/patches/projects/Gradle_AgentTest.kts +++ /dev/null @@ -1,12 +0,0 @@ -package Gradle_AgentTest.patches.projects - -import jetbrains.buildServer.configs.kotlin.v2018_2.* -import jetbrains.buildServer.configs.kotlin.v2018_2.ui.* - -/* -This patch script was generated by TeamCity on settings change in UI. -To apply the patch, remove the project with uuid = 'Gradle_AgentTest' (id = 'Gradle_AgentTest') -from your code, and delete the patch script. -*/ -deleteProject(uuid("Gradle_AgentTest")) - diff --git a/.teamcity/Gradle_Check/configurations/BaseGradleBuildType.kt b/.teamcity/Gradle_Check/configurations/BaseGradleBuildType.kt index e7e81dca491a9..c44d1a045a597 100644 --- a/.teamcity/Gradle_Check/configurations/BaseGradleBuildType.kt +++ b/.teamcity/Gradle_Check/configurations/BaseGradleBuildType.kt @@ -1,7 +1,7 @@ package configurations +import common.BuildCache import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType -import model.BuildCache import model.CIBuildModel import model.Stage diff --git a/.teamcity/Gradle_Check/configurations/FunctionalTest.kt b/.teamcity/Gradle_Check/configurations/FunctionalTest.kt index df6dc67f3b58e..cefbc48226787 100644 --- a/.teamcity/Gradle_Check/configurations/FunctionalTest.kt +++ b/.teamcity/Gradle_Check/configurations/FunctionalTest.kt @@ -1,8 +1,8 @@ package configurations +import common.Os import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId import model.CIBuildModel -import model.OS import model.Stage import model.TestCoverage import model.TestType @@ -33,7 +33,7 @@ class FunctionalTest(model: CIBuildModel, testCoverage: TestCoverage, subProject params { param("env.JAVA_HOME", "%${testCoverage.os}.${testCoverage.buildJvmVersion}.openjdk.64bit%") - if (testCoverage.os == OS.linux) { + if (testCoverage.os == Os.linux) { param("env.ANDROID_HOME", "/opt/android/sdk") } } diff --git a/.teamcity/Gradle_Check/configurations/GradleBuildConfigurationDefaults.kt b/.teamcity/Gradle_Check/configurations/GradleBuildConfigurationDefaults.kt index ee8aa1d1623c9..eec9af2ee9447 100644 --- a/.teamcity/Gradle_Check/configurations/GradleBuildConfigurationDefaults.kt +++ b/.teamcity/Gradle_Check/configurations/GradleBuildConfigurationDefaults.kt @@ -1,19 +1,23 @@ package configurations +import common.Os +import common.applyDefaultSettings +import common.buildToolGradleParameters +import common.buildToolParametersString +import common.checkCleanM2 +import common.compileAllDependency +import common.gradleWrapper +import common.verifyTestFilesCleanup import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId import jetbrains.buildServer.configs.kotlin.v2018_2.BuildFeatures import jetbrains.buildServer.configs.kotlin.v2018_2.BuildStep import jetbrains.buildServer.configs.kotlin.v2018_2.BuildSteps import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2018_2.CheckoutMode import jetbrains.buildServer.configs.kotlin.v2018_2.FailureAction import jetbrains.buildServer.configs.kotlin.v2018_2.ProjectFeatures import jetbrains.buildServer.configs.kotlin.v2018_2.buildFeatures.commitStatusPublisher -import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.GradleBuildStep -import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.script import model.CIBuildModel import model.GradleSubproject -import model.OS import model.TestCoverage @@ -24,19 +28,6 @@ fun shouldBeSkipped(subProject: GradleSubproject, testConfig: TestCoverage): Boo return testConfig.os.ignoredSubprojects.contains(subProject.name) } -fun gradleParameterString(daemon: Boolean = true) = gradleParameters(daemon).joinToString(separator = " ") - -fun gradleParameters(daemon: Boolean = true, isContinue: Boolean = true): List = - listOf( - "-PmaxParallelForks=%maxParallelForks%", - "-s", - if (daemon) "--daemon" else "--no-daemon", - if (isContinue) "--continue" else "", - """-I "%teamcity.build.checkoutDir%/gradle/init-scripts/build-scan.init.gradle.kts"""", - "-Dorg.gradle.internal.tasks.createops", - "-Dorg.gradle.internal.plugins.portal.url.override=http://dev12.gradle.org:8081/artifactory/gradle-plugins/") - - val m2CleanScriptUnixLike = """ REPO=%teamcity.agent.jvm.user.home%/.m2/repository if [ -e ${'$'}REPO ] ; then @@ -57,36 +48,6 @@ val m2CleanScriptWindows = """ ) """.trimIndent() -fun applyDefaultSettings(buildType: BuildType, os: OS = OS.linux, timeout: Int = 30, vcsRoot: String = "Gradle_Branches_GradlePersonalBranches") { - buildType.artifactRules = """ - build/report-* => . - buildSrc/build/report-* => . - subprojects/*/build/tmp/test files/** => test-files - build/errorLogs/** => errorLogs - build/reports/incubation/** => incubation-reports - """.trimIndent() - - buildType.vcs { - root(AbsoluteId(vcsRoot)) - checkoutMode = CheckoutMode.ON_AGENT - buildDefaultBranch = !vcsRoot.contains("Branches") - } - - buildType.requirements { - contains("teamcity.agent.jvm.os.name", os.agentRequirement) - } - - buildType.failureConditions { - executionTimeoutMin = timeout - } - - if (os == OS.linux || os == OS.macos) { - buildType.params { - param("env.LC_ALL", "en_US.UTF-8") - } - } -} - fun BuildFeatures.publishBuildStatusToGithub() { commitStatusPublisher { vcsRootExtId = "Gradle_Branches_GradlePersonalBranches" @@ -109,43 +70,23 @@ fun ProjectFeatures.buildReportTab(title: String, startPage: String) { } private -fun BaseGradleBuildType.checkCleanM2Step(os: OS = OS.linux) { - steps { - script { - name = "CHECK_CLEAN_M2" - executionMode = BuildStep.ExecutionMode.ALWAYS - scriptContent = if (os == OS.windows) m2CleanScriptWindows else m2CleanScriptUnixLike - } - } -} - -private -fun BaseGradleBuildType.verifyTestFilesCleanupStep(daemon: Boolean = true) { - steps { +fun BuildSteps.tagBuild(tagBuild: Boolean = true, daemon: Boolean = true) { + if (tagBuild) { gradleWrapper { - name = "VERIFY_TEST_FILES_CLEANUP" - tasks = "verifyTestFilesCleanup" - gradleParams = gradleParameterString(daemon) + name = "TAG_BUILD" + executionMode = BuildStep.ExecutionMode.ALWAYS + tasks = "tagBuild" + gradleParams = "${buildToolParametersString(daemon)} -PteamCityUsername=%teamcity.username.restbot% -PteamCityPassword=%teamcity.password.restbot% -PteamCityBuildId=%teamcity.build.id% -PgithubToken=%github.ci.oauth.token%" } } } -private -fun BaseGradleBuildType.tagBuildStep(model: CIBuildModel, daemon: Boolean = true) { - steps { - if (model.tagBuilds) { - gradleWrapper { - name = "TAG_BUILD" - executionMode = BuildStep.ExecutionMode.ALWAYS - tasks = "tagBuild" - gradleParams = "${gradleParameterString(daemon)} -PteamCityUsername=%teamcity.username.restbot% -PteamCityPassword=%teamcity.password.restbot% -PteamCityBuildId=%teamcity.build.id% -PgithubToken=%github.ci.oauth.token%" - } - } - } +fun BuildSteps.tagBuild(model: CIBuildModel, daemon: Boolean = true) { + tagBuild(tagBuild = model.tagBuilds, daemon = daemon) } private -fun BaseGradleBuildType.gradleRunnerStep(model: CIBuildModel, gradleTasks: String, os: OS = OS.linux, extraParameters: String = "", daemon: Boolean = true) { +fun BaseGradleBuildType.gradleRunnerStep(model: CIBuildModel, gradleTasks: String, os: Os = Os.linux, extraParameters: String = "", daemon: Boolean = true) { val buildScanTags = model.buildScanTags + listOfNotNull(stage?.id) steps { @@ -153,82 +94,83 @@ fun BaseGradleBuildType.gradleRunnerStep(model: CIBuildModel, gradleTasks: Strin name = "GRADLE_RUNNER" tasks = "clean $gradleTasks" gradleParams = ( - listOf(gradleParameterString(daemon)) + - this@gradleRunnerStep.buildCache.gradleParameters(os) + - listOf(extraParameters) + - "-PteamCityUsername=%teamcity.username.restbot%" + - "-PteamCityPassword=%teamcity.password.restbot%" + - "-PteamCityBuildId=%teamcity.build.id%" + - buildScanTags.map { configurations.buildScanTag(it) } - ).joinToString(separator = " ") + buildToolGradleParameters(daemon) + + this@gradleRunnerStep.buildCache.gradleParameters(os) + + listOf(extraParameters) + + "-PteamCityUsername=%teamcity.username.restbot%" + + "-PteamCityPassword=%teamcity.password.restbot%" + + "-PteamCityBuildId=%teamcity.build.id%" + + buildScanTags.map { configurations.buildScanTag(it) } + ).joinToString(separator = " ") } } } private -fun BaseGradleBuildType.gradleRerunnerStep(model: CIBuildModel, gradleTasks: String, os: OS = OS.linux, extraParameters: String = "", daemon: Boolean = true) { +fun BaseGradleBuildType.gradleRerunnerStep(model: CIBuildModel, gradleTasks: String, os: Os = Os.linux, extraParameters: String = "", daemon: Boolean = true) { val buildScanTags = model.buildScanTags + listOfNotNull(stage?.id) steps { gradleWrapper { name = "GRADLE_RERUNNER" - tasks = gradleTasks + tasks = "$gradleTasks tagBuild" executionMode = BuildStep.ExecutionMode.RUN_ON_FAILURE gradleParams = ( - listOf(gradleParameterString(daemon)) + - this@gradleRerunnerStep.buildCache.gradleParameters(os) + - listOf(extraParameters) + - "-PteamCityUsername=%teamcity.username.restbot%" + - "-PteamCityPassword=%teamcity.password.restbot%" + - "-PteamCityBuildId=%teamcity.build.id%" + - buildScanTags.map { configurations.buildScanTag(it) } + - "-PonlyPreviousFailedTestClasses=true" - ).joinToString(separator = " ") + buildToolGradleParameters(daemon) + + this@gradleRerunnerStep.buildCache.gradleParameters(os) + + listOf(extraParameters) + + "-PteamCityUsername=%teamcity.username.restbot%" + + "-PteamCityPassword=%teamcity.password.restbot%" + + "-PteamCityBuildId=%teamcity.build.id%" + + buildScanTags.map { configurations.buildScanTag(it) } + + "-PonlyPreviousFailedTestClasses=true" + + "-PgithubToken=%github.ci.oauth.token%" + ).joinToString(separator = " ") } } } private -fun BaseGradleBuildType.killProcessStepIfNecessary(stepName: String, os: OS = OS.linux, daemon: Boolean = true) { - if (os == OS.windows) { +fun BaseGradleBuildType.killProcessStepIfNecessary(stepName: String, os: Os = Os.linux, daemon: Boolean = true) { + if (os == Os.windows) { steps { gradleWrapper { name = stepName executionMode = BuildStep.ExecutionMode.ALWAYS tasks = "killExistingProcessesStartedByGradle" - gradleParams = gradleParameterString(daemon) + gradleParams = buildToolParametersString(daemon) } } } } -fun applyDefaults(model: CIBuildModel, buildType: BaseGradleBuildType, gradleTasks: String, notQuick: Boolean = false, os: OS = OS.linux, extraParameters: String = "", timeout: Int = 90, extraSteps: BuildSteps.() -> Unit = {}, daemon: Boolean = true) { - applyDefaultSettings(buildType, os, timeout) +fun applyDefaults(model: CIBuildModel, buildType: BaseGradleBuildType, gradleTasks: String, notQuick: Boolean = false, os: Os = Os.linux, extraParameters: String = "", timeout: Int = 90, extraSteps: BuildSteps.() -> Unit = {}, daemon: Boolean = true) { + buildType.applyDefaultSettings(os, timeout) buildType.gradleRunnerStep(model, gradleTasks, os, extraParameters, daemon) - buildType.steps.extraSteps() - - buildType.checkCleanM2Step(os) - buildType.verifyTestFilesCleanupStep(daemon) - buildType.tagBuildStep(model, daemon) + buildType.steps { + extraSteps() + checkCleanM2(os) + verifyTestFilesCleanup(daemon) + } applyDefaultDependencies(model, buildType, notQuick) } -fun applyTestDefaults(model: CIBuildModel, buildType: BaseGradleBuildType, gradleTasks: String, notQuick: Boolean = false, os: OS = OS.linux, extraParameters: String = "", timeout: Int = 90, extraSteps: BuildSteps.() -> Unit = {}, daemon: Boolean = true) { - applyDefaultSettings(buildType, os, timeout) +fun applyTestDefaults(model: CIBuildModel, buildType: BaseGradleBuildType, gradleTasks: String, notQuick: Boolean = false, os: Os = Os.linux, extraParameters: String = "", timeout: Int = 90, extraSteps: BuildSteps.() -> Unit = {}, daemon: Boolean = true) { + buildType.applyDefaultSettings(os, timeout) buildType.gradleRunnerStep(model, gradleTasks, os, extraParameters, daemon) buildType.killProcessStepIfNecessary("KILL_PROCESSES_STARTED_BY_GRADLE", os) buildType.gradleRerunnerStep(model, gradleTasks, os, extraParameters, daemon) buildType.killProcessStepIfNecessary("KILL_PROCESSES_STARTED_BY_GRADLE_RERUN", os) - buildType.steps.extraSteps() - - buildType.checkCleanM2Step(os) - buildType.verifyTestFilesCleanupStep(daemon) - buildType.tagBuildStep(model, daemon) + buildType.steps { + extraSteps() + checkCleanM2(os) + verifyTestFilesCleanup(daemon) + } applyDefaultDependencies(model, buildType, notQuick) } @@ -252,39 +194,7 @@ fun applyDefaultDependencies(model: CIBuildModel, buildType: BuildType, notQuick if (buildType !is CompileAll) { buildType.dependencies { - val compileAllId = CompileAll.buildTypeId(model) - // Compile All has to succeed before anything else is started - dependency(AbsoluteId(compileAllId)) { - snapshot { - onDependencyFailure = FailureAction.CANCEL - onDependencyCancel = FailureAction.CANCEL - } - } - // Get the build receipt from sanity check to reuse the timestamp - artifacts(AbsoluteId(compileAllId)) { - id = "ARTIFACT_DEPENDENCY_$compileAllId" - cleanDestination = true - artifactRules = "build-receipt.properties => incoming-distributions" - } + compileAllDependency(CompileAll.buildTypeId(model)) } } } - -/** - * Adds a [Gradle build step](https://confluence.jetbrains.com/display/TCDL/Gradle) - * that runs with the Gradle wrapper. - * - * @see GradleBuildStep - */ -fun BuildSteps.gradleWrapper(init: GradleBuildStep.() -> Unit): GradleBuildStep = - customGradle(init) { - useGradleWrapper = true - if (buildFile == null) { - buildFile = "" // Let Gradle detect the build script - } - } - -fun BuildSteps.customGradle(init: GradleBuildStep.() -> Unit, custom: GradleBuildStep.() -> Unit): GradleBuildStep = - GradleBuildStep(init) - .apply(custom) - .also { step(it) } diff --git a/.teamcity/Gradle_Check/configurations/Gradleception.kt b/.teamcity/Gradle_Check/configurations/Gradleception.kt index 0e702ad776cbd..f4fc785c5e7be 100644 --- a/.teamcity/Gradle_Check/configurations/Gradleception.kt +++ b/.teamcity/Gradle_Check/configurations/Gradleception.kt @@ -1,5 +1,7 @@ package configurations +import common.buildToolGradleParameters +import common.customGradle import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId import jetbrains.buildServer.configs.kotlin.v2018_2.BuildSteps import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.GradleBuildStep @@ -17,7 +19,7 @@ class Gradleception(model: CIBuildModel, stage: Stage) : BaseGradleBuildType(mod } val buildScanTagForType = buildScanTag("Gradleception") - val defaultParameters = (gradleParameters() + listOf(buildScanTagForType)).joinToString(separator = " ") + val defaultParameters = (buildToolGradleParameters() + listOf(buildScanTagForType)).joinToString(separator = " ") applyDefaults(model, this, ":install", notQuick = true, extraParameters = "-Pgradle_installPath=dogfood-first $buildScanTagForType", extraSteps = { localGradle { diff --git a/.teamcity/Gradle_Check/configurations/IndividualPerformanceScenarioWorkers.kt b/.teamcity/Gradle_Check/configurations/IndividualPerformanceScenarioWorkers.kt index 3f1d7dcfda66e..b56a110890a13 100644 --- a/.teamcity/Gradle_Check/configurations/IndividualPerformanceScenarioWorkers.kt +++ b/.teamcity/Gradle_Check/configurations/IndividualPerformanceScenarioWorkers.kt @@ -1,17 +1,19 @@ package configurations +import common.Os +import common.applyDefaultSettings +import common.buildToolGradleParameters +import common.checkCleanM2 +import common.gradleWrapper import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId -import jetbrains.buildServer.configs.kotlin.v2018_2.BuildStep -import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.script import model.CIBuildModel -import model.OS class IndividualPerformanceScenarioWorkers(model: CIBuildModel) : BaseGradleBuildType(model, init = { uuid = model.projectPrefix + "IndividualPerformanceScenarioWorkersLinux" id = AbsoluteId(uuid) name = "Individual Performance Scenario Workers - Linux" - applyDefaultSettings(this, timeout = 420) + applyDefaultSettings(timeout = 420) artifactRules = """ subprojects/*/build/test-results-*.zip => results subprojects/*/build/tmp/**/log.txt => failure-logs @@ -41,17 +43,13 @@ class IndividualPerformanceScenarioWorkers(model: CIBuildModel) : BaseGradleBuil name = "GRADLE_RUNNER" tasks = "" gradleParams = ( - gradleParameters(isContinue = false) + buildToolGradleParameters(isContinue = false) + listOf("""clean %templates% fullPerformanceTests --scenarios "%scenario%" --baselines %baselines% --warmups %warmups% --runs %runs% --checks %checks% --channel %channel% -x prepareSamples -Porg.gradle.performance.branchName=%teamcity.build.branch% -Porg.gradle.performance.db.url=%performance.db.url% -Porg.gradle.performance.db.username=%performance.db.username% -Porg.gradle.performance.db.password=%performance.db.password.tcagent% -PtimestampedVersion""", buildScanTag("IndividualPerformanceScenarioWorkers"), "-PtestJavaHome=${individualPerformanceTestJavaHome}") - + model.parentBuildCache.gradleParameters(OS.linux) + + model.parentBuildCache.gradleParameters(Os.linux) ).joinToString(separator = " ") } - script { - name = "CHECK_CLEAN_M2" - executionMode = BuildStep.ExecutionMode.ALWAYS - scriptContent = m2CleanScriptUnixLike - } + checkCleanM2() } applyDefaultDependencies(model, this) diff --git a/.teamcity/Gradle_Check/configurations/PerformanceTest.kt b/.teamcity/Gradle_Check/configurations/PerformanceTest.kt index 7775e1f8490f2..783eba46bd749 100644 --- a/.teamcity/Gradle_Check/configurations/PerformanceTest.kt +++ b/.teamcity/Gradle_Check/configurations/PerformanceTest.kt @@ -1,10 +1,14 @@ package configurations +import common.Os +import common.applyPerformanceTestSettings +import common.buildToolGradleParameters +import common.checkCleanM2 +import common.distributedPerformanceTestParameters +import common.gradleWrapper +import common.performanceTestCommandLine import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId -import jetbrains.buildServer.configs.kotlin.v2018_2.BuildStep -import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.script import model.CIBuildModel -import model.OS import model.PerformanceTestType import model.Stage @@ -13,11 +17,7 @@ class PerformanceTest(model: CIBuildModel, type: PerformanceTestType, stage: Sta id = AbsoluteId(uuid) name = "Performance ${type.name.capitalize()} Coordinator - Linux" - applyDefaultSettings(this, timeout = type.timeout) - artifactRules = """ - build/report-*-performance-tests.zip => . - """.trimIndent() - detectHangingBuilds = false + applyPerformanceTestSettings(timeout = type.timeout) if (type == PerformanceTestType.test) { features { @@ -27,12 +27,6 @@ class PerformanceTest(model: CIBuildModel, type: PerformanceTestType, stage: Sta params { param("performance.baselines", type.defaultBaselines) - param("env.GRADLE_OPTS", "-Xmx1536m -XX:MaxPermSize=384m") - param("env.JAVA_HOME", buildJavaHome) - param("env.BUILD_BRANCH", "%teamcity.build.branch%") - param("performance.db.url", "jdbc:h2:ssl://dev61.gradle.org:9092") - param("performance.db.username", "tcagent") - param("TC_USERNAME", "TeamcityRestBot") } steps { @@ -40,25 +34,15 @@ class PerformanceTest(model: CIBuildModel, type: PerformanceTestType, stage: Sta name = "GRADLE_RUNNER" tasks = "" gradleParams = ( - gradleParameters() - + listOf("clean distributed${type.taskId}s -x prepareSamples --baselines %performance.baselines% ${type.extraParameters} -PtimestampedVersion -Porg.gradle.performance.branchName=%teamcity.build.branch% -Porg.gradle.performance.db.url=%performance.db.url% -Porg.gradle.performance.db.username=%performance.db.username% -PteamCityUsername=%TC_USERNAME% -PteamCityPassword=%teamcity.password.restbot% -Porg.gradle.performance.buildTypeId=${IndividualPerformanceScenarioWorkers(model).id} -Porg.gradle.performance.workerTestTaskName=fullPerformanceTest -Porg.gradle.performance.coordinatorBuildId=%teamcity.build.id% -Porg.gradle.performance.db.password=%performance.db.password.tcagent%", - buildScanTag("PerformanceTest"), "-PtestJavaHome=${coordinatorPerformanceTestJavaHome}") - + model.parentBuildCache.gradleParameters(OS.linux) + buildToolGradleParameters(isContinue = false) + + performanceTestCommandLine(task = "distributed${type.taskId}s", baselines = "%performance.baselines%", extraParameters = type.extraParameters) + + distributedPerformanceTestParameters(IndividualPerformanceScenarioWorkers(model).id.toString()) + + listOf(buildScanTag("PerformanceTest")) + + model.parentBuildCache.gradleParameters(Os.linux) ).joinToString(separator = " ") } - script { - name = "CHECK_CLEAN_M2" - executionMode = BuildStep.ExecutionMode.ALWAYS - scriptContent = m2CleanScriptUnixLike - } - if (model.tagBuilds) { - gradleWrapper { - name = "TAG_BUILD" - executionMode = BuildStep.ExecutionMode.ALWAYS - tasks = "tagBuild" - gradleParams = "-PteamCityUsername=%teamcity.username.restbot% -PteamCityPassword=%teamcity.password.restbot% -PteamCityBuildId=%teamcity.build.id% -PgithubToken=%github.ci.oauth.token%" - } - } + checkCleanM2() + tagBuild(model, true) } applyDefaultDependencies(model, this, true) diff --git a/.teamcity/Gradle_Check/configurations/StagePasses.kt b/.teamcity/Gradle_Check/configurations/StagePasses.kt index f5f5642b0e0ec..8a4e80c4e3122 100644 --- a/.teamcity/Gradle_Check/configurations/StagePasses.kt +++ b/.teamcity/Gradle_Check/configurations/StagePasses.kt @@ -1,5 +1,7 @@ package configurations +import common.applyDefaultSettings +import common.gradleWrapper import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId import jetbrains.buildServer.configs.kotlin.v2018_2.BuildStep import jetbrains.buildServer.configs.kotlin.v2018_2.FailureAction @@ -19,7 +21,7 @@ class StagePasses(model: CIBuildModel, stage: Stage, prevStage: Stage?, contains id = stageTriggerId(model, stage) name = stage.stageName.stageName + " (Trigger)" - applyDefaultSettings(this) + applyDefaultSettings() artifactRules = "build/build-receipt.properties" val triggerExcludes = """ @@ -29,7 +31,7 @@ class StagePasses(model: CIBuildModel, stage: Stage, prevStage: Stage?, contains -:.teamcityTest -:subprojects/docs/src/docs/release """.trimIndent() - val masterReleaseFiler = model.masterAndReleaseBranches.joinToString(prefix = "+:", separator = "\n+:") + val masterReleaseFilter = model.masterAndReleaseBranches.joinToString(prefix = "+:", separator = "\n+:") if (model.publishStatusToGitHub) { features { @@ -42,7 +44,7 @@ class StagePasses(model: CIBuildModel, stage: Stage, prevStage: Stage?, contains quietPeriodMode = VcsTrigger.QuietPeriodMode.USE_CUSTOM quietPeriod = 90 triggerRules = triggerExcludes - branchFilter = masterReleaseFiler + branchFilter = masterReleaseFilter } } else if (stage.trigger != Trigger.never) { triggers.schedule { @@ -60,7 +62,7 @@ class StagePasses(model: CIBuildModel, stage: Stage, prevStage: Stage?, contains triggerBuild = always() withPendingChangesOnly = true param("revisionRule", "lastFinished") - param("branchFilter", masterReleaseFiler) + param("branchFilter", masterReleaseFilter) } } @@ -85,7 +87,7 @@ class StagePasses(model: CIBuildModel, stage: Stage, prevStage: Stage?, contains name = "TAG_BUILD" executionMode = BuildStep.ExecutionMode.ALWAYS tasks = "tagBuild" - gradleParams = "-PteamCityUsername=%teamcity.username.restbot% -PteamCityPassword=%teamcity.password.restbot% -PteamCityBuildId=%teamcity.build.id% -PgithubToken=%github.ci.oauth.token% ${buildScanTag("StagePasses")}" + gradleParams = "-PteamCityUsername=%teamcity.username.restbot% -PteamCityPassword=%teamcity.password.restbot% -PteamCityBuildId=%teamcity.build.id% -PgithubToken=%github.ci.oauth.token% ${buildScanTag("StagePasses")} --daemon" } } } diff --git a/.teamcity/Gradle_Check/model/CIBuildModel.kt b/.teamcity/Gradle_Check/model/CIBuildModel.kt index 66c72f058454f..bca9de5d7233b 100644 --- a/.teamcity/Gradle_Check/model/CIBuildModel.kt +++ b/.teamcity/Gradle_Check/model/CIBuildModel.kt @@ -1,5 +1,11 @@ package model +import common.BuildCache +import common.JvmCategory +import common.JvmVendor +import common.JvmVersion +import common.Os +import common.builtInRemoteBuildCacheNode import configurations.BuildDistributions import configurations.CompileAll import configurations.DependenciesCheck @@ -21,23 +27,23 @@ enum class StageNames(override val stageName: String, override val description: data class CIBuildModel ( - val projectPrefix: String = "Gradle_Check_", - val rootProjectName: String = "Check", - val tagBuilds: Boolean = true, - val publishStatusToGitHub: Boolean = true, - val masterAndReleaseBranches: List = listOf("master", "release"), - val parentBuildCache: BuildCache = RemoteBuildCache("%gradle.cache.remote.url%"), - val childBuildCache: BuildCache = RemoteBuildCache("%gradle.cache.remote.url%"), - val buildScanTags: List = emptyList(), - val stages: List = listOf( + val projectPrefix: String = "Gradle_Check_", + val rootProjectName: String = "Check", + val tagBuilds: Boolean = true, + val publishStatusToGitHub: Boolean = true, + val masterAndReleaseBranches: List = listOf("master", "release"), + val parentBuildCache: BuildCache = builtInRemoteBuildCacheNode, + val childBuildCache: BuildCache = builtInRemoteBuildCacheNode, + val buildScanTags: List = emptyList(), + val stages: List = listOf( Stage(StageNames.QUICK_FEEDBACK_LINUX_ONLY, specificBuilds = listOf( SpecificBuild.CompileAll, SpecificBuild.SanityCheck), functionalTests = listOf( - TestCoverage(TestType.quick, OS.linux, JvmVersion.java11, vendor = JvmVendor.openjdk)), omitsSlowProjects = true), + TestCoverage(1, TestType.quick, Os.linux, common.JvmCategory.MAX_VERSION.version, vendor = common.JvmCategory.MAX_VERSION.vendor)), omitsSlowProjects = true), Stage(StageNames.QUICK_FEEDBACK, functionalTests = listOf( - TestCoverage(TestType.quick, OS.windows, JvmVersion.java8)), + TestCoverage(2, TestType.quick, Os.windows, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor)), functionalTestsDependOnSpecificBuilds = true, omitsSlowProjects = true, dependsOnSanityCheck = true), @@ -47,28 +53,28 @@ data class CIBuildModel ( SpecificBuild.Gradleception, SpecificBuild.SmokeTests), functionalTests = listOf( - TestCoverage(TestType.platform, OS.linux, JvmVersion.java8), - TestCoverage(TestType.platform, OS.windows, JvmVersion.java11, vendor = JvmVendor.openjdk)), + TestCoverage(3, TestType.platform, Os.linux, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor), + TestCoverage(4, TestType.platform, Os.windows, JvmCategory.MAX_VERSION.version, vendor = JvmCategory.MAX_VERSION.vendor)), performanceTests = listOf(PerformanceTestType.test), omitsSlowProjects = true), Stage(StageNames.READY_FOR_NIGHTLY, trigger = Trigger.eachCommit, functionalTests = listOf( - TestCoverage(TestType.quickFeedbackCrossVersion, OS.linux, JvmVersion.java8), - TestCoverage(TestType.quickFeedbackCrossVersion, OS.windows, JvmVersion.java8), - TestCoverage(TestType.parallel, OS.linux, JvmVersion.java11, vendor = JvmVendor.openjdk)) + TestCoverage(5, TestType.quickFeedbackCrossVersion, Os.linux, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor), + TestCoverage(6, TestType.quickFeedbackCrossVersion, Os.windows, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor), + TestCoverage(7, TestType.parallel, Os.linux, JvmCategory.MAX_VERSION.version, vendor = JvmCategory.MAX_VERSION.vendor)) ), Stage(StageNames.READY_FOR_RELEASE, trigger = Trigger.daily, functionalTests = listOf( - TestCoverage(TestType.soak, OS.linux, JvmVersion.java11, vendor = JvmVendor.openjdk), - TestCoverage(TestType.soak, OS.windows, JvmVersion.java8), - TestCoverage(TestType.allVersionsCrossVersion, OS.linux, JvmVersion.java8), - TestCoverage(TestType.allVersionsCrossVersion, OS.windows, JvmVersion.java8), - TestCoverage(TestType.noDaemon, OS.linux, JvmVersion.java8), - TestCoverage(TestType.noDaemon, OS.windows, JvmVersion.java11, vendor = JvmVendor.openjdk), - TestCoverage(TestType.platform, OS.macos, JvmVersion.java8), - TestCoverage(TestType.forceRealizeDependencyManagement, OS.linux, JvmVersion.java8)), + TestCoverage(8, TestType.soak, Os.linux, JvmCategory.MAX_VERSION.version, vendor = JvmCategory.MAX_VERSION.vendor), + TestCoverage(9, TestType.soak, Os.windows, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor), + TestCoverage(10,TestType.allVersionsCrossVersion, Os.linux, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor), + TestCoverage(11,TestType.allVersionsCrossVersion, Os.windows, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor), + TestCoverage(12, TestType.noDaemon, Os.linux, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor), + TestCoverage(13, TestType.noDaemon, Os.windows, JvmCategory.MAX_VERSION.version, vendor = JvmCategory.MAX_VERSION.vendor), + TestCoverage(14, TestType.platform, Os.macos, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor), + TestCoverage(15, TestType.forceRealizeDependencyManagement, Os.linux, JvmCategory.MIN_VERSION.version, vendor = JvmCategory.MIN_VERSION.vendor)), performanceTests = listOf( PerformanceTestType.experiment)), Stage(StageNames.HISTORICAL_PERFORMANCE, @@ -79,11 +85,13 @@ data class CIBuildModel ( trigger = Trigger.never, runsIndependent = true, functionalTests = listOf( - TestCoverage(TestType.platform, OS.linux, JvmVersion.java12, vendor = JvmVendor.openjdk), - TestCoverage(TestType.platform, OS.windows, JvmVersion.java12, vendor = JvmVendor.openjdk)) + TestCoverage(16, TestType.quick, Os.linux, JvmCategory.EXPERIMENTAL_VERSION.version, vendor = JvmCategory.EXPERIMENTAL_VERSION.vendor), + TestCoverage(17, TestType.quick, Os.windows, JvmCategory.EXPERIMENTAL_VERSION.version, vendor = JvmCategory.EXPERIMENTAL_VERSION.vendor), + TestCoverage(18, TestType.platform, Os.linux, JvmCategory.EXPERIMENTAL_VERSION.version, vendor = JvmCategory.EXPERIMENTAL_VERSION.vendor), + TestCoverage(19, TestType.platform, Os.windows, JvmCategory.EXPERIMENTAL_VERSION.version, vendor = JvmCategory.EXPERIMENTAL_VERSION.vendor)) ) ), - val subProjects : List = listOf( + val subProjects : List = listOf( GradleSubproject("announce"), GradleSubproject("antlr"), GradleSubproject("baseServices"), @@ -164,9 +172,9 @@ data class CIBuildModel ( GradleSubproject("apiMetadata", unitTests = false, functionalTests = false), GradleSubproject("kotlinDsl", unitTests = true, functionalTests = true), - GradleSubproject("kotlinDslProviderPlugins", unitTests = false, functionalTests = false), + GradleSubproject("kotlinDslProviderPlugins", unitTests = true, functionalTests = true), GradleSubproject("kotlinDslToolingModels", unitTests = false, functionalTests = false), - GradleSubproject("kotlinDslToolingBuilders", unitTests = true, functionalTests = true), + GradleSubproject("kotlinDslToolingBuilders", unitTests = true, functionalTests = true, crossVersionTests = true), GradleSubproject("kotlinDslPlugins", unitTests = true, functionalTests = true), GradleSubproject("kotlinDslTestFixtures", unitTests = true, functionalTests = false), GradleSubproject("kotlinDslIntegTests", unitTests = false, functionalTests = true), @@ -192,29 +200,6 @@ data class GradleSubproject(val name: String, val unitTests: Boolean = true, val fun hasTestsOf(type: TestType) = (unitTests && type.unitTests) || (functionalTests && type.functionalTests) || (crossVersionTests && type.crossVersionTests) } -interface BuildCache { - fun gradleParameters(os: OS): List -} - -data class RemoteBuildCache(val url: String, val username: String = "%gradle.cache.remote.username%", val password: String = "%gradle.cache.remote.password%") : BuildCache { - override fun gradleParameters(os: OS): List { - return listOf("--build-cache", - os.escapeKeyValuePair("-Dgradle.cache.remote.url", url), - os.escapeKeyValuePair("-Dgradle.cache.remote.username", username), - os.escapeKeyValuePair("-Dgradle.cache.remote.password", password) - ) - } -} - -private -fun OS.escapeKeyValuePair(key: String, value: String) = if (this == OS.windows) """$key="$value"""" else """"$key=$value"""" - -object NoBuildCache : BuildCache { - override fun gradleParameters(os: OS): List { - return emptyList() - } -} - interface StageName { val stageName: String val description: String @@ -228,14 +213,14 @@ data class Stage(val stageName: StageName, val specificBuilds: List = emptyList()) { - linux("Linux"), windows("Windows"), macos("Mac", listOf("integTest", "native", "plugins", "resources", "scala", "workers", "wrapper", "platformPlay")) -} - -enum class JvmVersion { - java8, java9, java10, java11, java12 -} - enum class TestType(val unitTests: Boolean = true, val functionalTests: Boolean = true, val crossVersionTests: Boolean = false, val timeout: Int = 180) { // Include cross version tests, these take care of selecting a very small set of versions to cover when run as part of this stage, including the current version quick(true, true, true, 60), @@ -280,10 +257,6 @@ enum class TestType(val unitTests: Boolean = true, val functionalTests: Boolean forceRealizeDependencyManagement(false, true, false) } -enum class JvmVendor { - oracle, ibm, openjdk -} - enum class PerformanceTestType(val taskId: String, val timeout : Int, val defaultBaselines: String = "", val extraParameters : String = "") { test("PerformanceTest", 420, "defaults"), experiment("PerformanceExperiment", 420, "defaults"), diff --git a/.teamcity/Gradle_Check/pluginData/plugin-settings.xml b/.teamcity/Gradle_Check/pluginData/plugin-settings.xml index 4d0460b3c6a2d..f8bace73a1954 100644 --- a/.teamcity/Gradle_Check/pluginData/plugin-settings.xml +++ b/.teamcity/Gradle_Check/pluginData/plugin-settings.xml @@ -3,14 +3,14 @@ + + + - - - - + diff --git a/.teamcity/Gradle_Check_dsl.iml b/.teamcity/Gradle_Check_dsl.iml index 5ac25c89f3a58..2f4ef73d6781c 100644 --- a/.teamcity/Gradle_Check_dsl.iml +++ b/.teamcity/Gradle_Check_dsl.iml @@ -5,14 +5,17 @@ - + @@ -32,15 +35,32 @@ - + + + + + + + + + + + + + + + + + + @@ -58,14 +78,25 @@ - - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.teamcity/Gradle_Promotion/Project.kt b/.teamcity/Gradle_Promotion/Project.kt new file mode 100644 index 0000000000000..c0e3ff4081a5d --- /dev/null +++ b/.teamcity/Gradle_Promotion/Project.kt @@ -0,0 +1,80 @@ +package Gradle_Promotion + +import Gradle_Promotion.buildTypes.MasterSanityCheck +import Gradle_Promotion.buildTypes.PublishBranchSnapshotFromQuickFeedback +import Gradle_Promotion.buildTypes.PublishFinalRelease +import Gradle_Promotion.buildTypes.PublishMilestone +import Gradle_Promotion.buildTypes.PublishNightlySnapshot +import Gradle_Promotion.buildTypes.PublishNightlySnapshotFromQuickFeedback +import Gradle_Promotion.buildTypes.PublishReleaseCandidate +import Gradle_Promotion.buildTypes.StartReleaseCycle +import Gradle_Promotion.buildTypes.StartReleaseCycleTest +import Gradle_Promotion.vcsRoots.Gradle_Promotion_GradlePromotionBranches +import Gradle_Promotion.vcsRoots.Gradle_Promotion__master_ +import jetbrains.buildServer.configs.kotlin.v2018_2.Project +import jetbrains.buildServer.configs.kotlin.v2018_2.projectFeatures.VersionedSettings +import jetbrains.buildServer.configs.kotlin.v2018_2.projectFeatures.versionedSettings + +object Project : Project({ + uuid = "16c9f3e3-36a9-4596-a35c-70a3c7a2c5c8" + id("Gradle_Promotion") + parentId("Gradle") + name = "Promotion" + + vcsRoot(Gradle_Promotion_GradlePromotionBranches) + vcsRoot(Gradle_Promotion__master_) + + val nightlyMasterSnapshot = PublishNightlySnapshot(uuid = "01432c63-861f-4d08-ae0a-7d127f63096e", branch = "master", hour = 0) + val masterSnapshotFromQuickFeedback = PublishNightlySnapshotFromQuickFeedback(uuid = "9a55bec1-4e70-449b-8f45-400093505afb", branch = "master") + val nightlyReleaseSnapshot = PublishNightlySnapshot(uuid = "1f5ca7f8-b0f5-41f9-9ba7-6d518b2822f0", branch = "release", hour = 1) + val releaseSnapshotFromQuickFeedback = PublishNightlySnapshotFromQuickFeedback(uuid = "eeff4410-1e7d-4db6-b7b8-34c1f2754477", branch = "release") + + buildType(PublishBranchSnapshotFromQuickFeedback) + buildType(PublishMilestone) + buildType(PublishReleaseCandidate) + buildType(nightlyReleaseSnapshot) + buildType(StartReleaseCycle) + buildType(PublishFinalRelease) + buildType(nightlyMasterSnapshot) + buildType(StartReleaseCycleTest) + buildType(MasterSanityCheck) + buildType(masterSnapshotFromQuickFeedback) + buildType(releaseSnapshotFromQuickFeedback) + + params { + password("env.ORG_GRADLE_PROJECT_gradleS3SecretKey", "credentialsJSON:0f1f842f-df6c-4db7-8271-f1f73c823aed") + password("env.ORG_GRADLE_PROJECT_artifactoryUserPassword", "credentialsJSON:2b7529cd-77cd-49f4-9416-9461f6ac9018") + param("env.ORG_GRADLE_PROJECT_gradleS3AccessKey", "AKIAJUN6ZAPAEO3BC7AQ") + password("env.DOTCOM_DEV_DOCS_AWS_SECRET_KEY", "credentialsJSON:ed0db35e-2034-444c-a9b1-d966b9abe89b") + param("env.DOTCOM_DEV_DOCS_AWS_ACCESS_KEY", "AKIAJFJBF5BXLBNI3E5A") + password("env.ORG_GRADLE_PROJECT_sdkmanToken", "credentialsJSON:64e60515-68db-4bbd-aeae-ba2e058ac3cb") + param("env.JAVA_HOME", "%linux.java11.openjdk.64bit%") + param("env.ORG_GRADLE_PROJECT_artifactoryUserName", "bot-build-tool") + password("env.ORG_GRADLE_PROJECT_infrastructureEmailPwd", "credentialsJSON:ea637ef1-7607-40a4-be39-ef1aa8bc5af0") + param("env.ORG_GRADLE_PROJECT_sdkmanKey", "8ed1a771bc236c287ad93c699bfdd2d7") + } + + features { + versionedSettings { + id = "PROJECT_EXT_15" + mode = VersionedSettings.Mode.ENABLED + buildSettingsMode = VersionedSettings.BuildSettingsMode.PREFER_SETTINGS_FROM_VCS + rootExtId = "Gradle_Branches_VersionedSettings" + showChanges = false + settingsFormat = VersionedSettings.Format.KOTLIN + storeSecureParamsOutsideOfVcs = true + } + } + buildTypesOrder = arrayListOf( + MasterSanityCheck, + nightlyMasterSnapshot, + masterSnapshotFromQuickFeedback, + StartReleaseCycle, + nightlyReleaseSnapshot, + releaseSnapshotFromQuickFeedback, + PublishBranchSnapshotFromQuickFeedback, + PublishMilestone, + PublishReleaseCandidate, + PublishFinalRelease + ) +}) diff --git a/.teamcity/Gradle_Promotion/buildTypes/BasePromotionBuildType.kt b/.teamcity/Gradle_Promotion/buildTypes/BasePromotionBuildType.kt new file mode 100644 index 0000000000000..802ce2546ed1f --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/BasePromotionBuildType.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package Gradle_Promotion.buildTypes + +import common.Os +import common.requiresOs +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2018_2.CheckoutMode +import jetbrains.buildServer.configs.kotlin.v2018_2.vcs.GitVcsRoot + +abstract class BasePromotionBuildType(vcsRoot: GitVcsRoot, cleanCheckout: Boolean = true) : BuildType() { + init { + vcs { + root(vcsRoot) + + checkoutMode = CheckoutMode.ON_AGENT + this.cleanCheckout = cleanCheckout + showDependenciesChanges = true + } + + requirements { + requiresOs(Os.linux) + } + } +} diff --git a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_AllBranchesStartReleaseCycleTest.xml b/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_AllBranchesStartReleaseCycleTest.xml deleted file mode 100644 index 3ad320d09e391..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_AllBranchesStartReleaseCycleTest.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - Master - Start Release Cycle Test - Test for Start Release Cycle pipeline - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_FinalRelease.xml b/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_FinalRelease.xml deleted file mode 100644 index b150db422f24f..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_FinalRelease.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - Release - Final - Promotes the latest successful change on 'release' as a new release - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MasterNightlySnapshotManual.xml b/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MasterNightlySnapshotManual.xml deleted file mode 100644 index c71c53423012e..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MasterNightlySnapshotManual.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - Master - Nightly Snapshot (from Quick Feedback) - Promotes the latest change on 'master' that passed the 'Quick Feedback' stage as new nightly. This build configuration can be triggered manually if there are issues further down the pipeline we can ignore temporarily. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MasterSanityCheck.xml b/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MasterSanityCheck.xml deleted file mode 100644 index e2acce98e8cf5..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MasterSanityCheck.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - Master - Sanity Check - Compilation and test execution of buildSrc - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MilestoneMaster.xml b/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MilestoneMaster.xml deleted file mode 100644 index 4317a12635aa2..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_MilestoneMaster.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - Release - Milestone - Promotes the latest successful change on 'release' as the new snapshot - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_PublishBranchSnapshotFromQuickFeedback.xml b/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_PublishBranchSnapshotFromQuickFeedback.xml deleted file mode 100644 index d5fca066fc333..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_PublishBranchSnapshotFromQuickFeedback.xml +++ /dev/null @@ -1,42 +0,0 @@ - - - Publish Branch Snapshot (from Quick Feedback) - Deploys a new wrapper for the selected build/branch. Does not update master or the documentation. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_ReleaseSnapshotFromQuickFeedback.xml b/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_ReleaseSnapshotFromQuickFeedback.xml deleted file mode 100644 index ca9d323d8d2ee..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_ReleaseSnapshotFromQuickFeedback.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - Release - Release Nightly Snapshot (from Quick Feedback) - Deploys the latest successful change on 'release' as a new release nightly snapshot - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_StartReleaseCycle.xml b/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_StartReleaseCycle.xml deleted file mode 100644 index 3decd3a85780f..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/Gradle_Promotion_StartReleaseCycle.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - Master - Start Release Cycle - Promotes a successful build on master as the start of a new release cycle on the release branch - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/MasterSanityCheck.kt b/.teamcity/Gradle_Promotion/buildTypes/MasterSanityCheck.kt new file mode 100644 index 0000000000000..0e2afbf7220eb --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/MasterSanityCheck.kt @@ -0,0 +1,41 @@ +package Gradle_Promotion.buildTypes + +import common.Os +import common.gradleWrapper +import common.requiresOs +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2018_2.CheckoutMode +import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.vcs + +object MasterSanityCheck : BuildType({ + uuid = "bf9b573a-6e5e-4db1-88b2-399e709026b5" + id("Gradle_Promotion_MasterSanityCheck") + name = "Master - Sanity Check" + description = "Compilation and test execution of buildSrc" + + vcs { + root(Gradle_Promotion.vcsRoots.Gradle_Promotion__master_) + + checkoutMode = CheckoutMode.ON_AGENT + cleanCheckout = true + showDependenciesChanges = true + } + + steps { + gradleWrapper { + tasks = "tasks" + gradleParams = "-Igradle/buildScanInit.gradle" + param("org.jfrog.artifactory.selectedDeployableServer.defaultModuleVersionConfiguration", "GLOBAL") + } + } + + triggers { + vcs { + branchFilter = "" + } + } + + requirements { + requiresOs(Os.linux) + } +}) diff --git a/.teamcity/Gradle_Promotion/buildTypes/PublishBranchSnapshotFromQuickFeedback.kt b/.teamcity/Gradle_Promotion/buildTypes/PublishBranchSnapshotFromQuickFeedback.kt new file mode 100644 index 0000000000000..9deab9fb5f1dd --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/PublishBranchSnapshotFromQuickFeedback.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package Gradle_Promotion.buildTypes + +import jetbrains.buildServer.configs.kotlin.v2018_2.ParameterDisplay + +object PublishBranchSnapshotFromQuickFeedback : PublishGradleDistribution( + branch = "%branch.to.promote%", + triggerName = "QuickFeedback", + task = "promoteSnapshot", + extraParameters = "-PpromotedBranch=%branch.qualifier% ", + vcsRoot = Gradle_Promotion.vcsRoots.Gradle_Promotion_GradlePromotionBranches +) { + init { + uuid = "b7ecebd3-3812-4532-aa77-5679f9e9d6b3" + id("Gradle_Promotion_PublishBranchSnapshotFromQuickFeedback") + name = "Publish Branch Snapshot (from Quick Feedback)" + description = "Deploys a new distribution snapshot for the selected build/branch. Does not update master or the documentation." + + val triggerName = this.triggerName + + params { + param("branch.qualifier", "%dep.Gradle_Check_Stage_${triggerName}_Trigger.teamcity.build.branch%") + text("branch.to.promote", "%branch.qualifier%", label = "Branch to promote", description = "Type in the branch of gradle/gradle you want to promote. Leave the default value when promoting an existing build.", display = ParameterDisplay.PROMPT, allowEmpty = false) + } + } +} diff --git a/.teamcity/Gradle_Promotion/buildTypes/PublishGradleDistribution.kt b/.teamcity/Gradle_Promotion/buildTypes/PublishGradleDistribution.kt new file mode 100644 index 0000000000000..f36a39723c9b2 --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/PublishGradleDistribution.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package Gradle_Promotion.buildTypes + +import common.Os +import common.builtInRemoteBuildCacheNode +import common.gradleWrapper +import common.requiresOs +import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId +import jetbrains.buildServer.configs.kotlin.v2018_2.vcs.GitVcsRoot + +abstract class PublishGradleDistribution( + branch: String, + task: String, + val triggerName: String, + gitUserName: String = "Gradleware Git Bot", + gitUserEmail: String = "gradlewaregitbot@gradleware.com", + extraParameters: String = "", + vcsRoot: GitVcsRoot = Gradle_Promotion.vcsRoots.Gradle_Promotion__master_ +) : BasePromotionBuildType(vcsRoot = vcsRoot) { + + init { + artifactRules = """ + incoming-build-receipt/build-receipt.properties => incoming-build-receipt + **/build/git-checkout/build/build-receipt.properties + **/build/distributions/*.zip => promote-build-distributions + **/build/website-checkout/data/releases.xml + **/build/git-checkout/build/reports/integTest/** => distribution-tests + **/smoke-tests/build/reports/tests/** => post-smoke-tests + """.trimIndent() + + steps { + gradleWrapper { + name = "Promote" + tasks = task + gradleParams = """-PuseBuildReceipt $extraParameters "-PgitUserName=$gitUserName" "-PgitUserEmail=$gitUserEmail" -Igradle/buildScanInit.gradle ${builtInRemoteBuildCacheNode.gradleParameters(Os.linux).joinToString(" ")}""" + } + } + dependencies { + artifacts(AbsoluteId("Gradle_Check_Stage_${this@PublishGradleDistribution.triggerName}_Trigger")) { + buildRule = lastSuccessful(branch) + cleanDestination = true + artifactRules = "build-receipt.properties => incoming-build-receipt/" + } + } + + requirements { + requiresOs(Os.linux) + } + } +} + +fun String.promoteNightlyTaskName(): String = "promote${if (this == "master") "" else capitalize()}Nightly" diff --git a/.teamcity/Gradle_Promotion/buildTypes/PublishNightlySnapshot.kt b/.teamcity/Gradle_Promotion/buildTypes/PublishNightlySnapshot.kt new file mode 100644 index 0000000000000..c88ebc8185e5d --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/PublishNightlySnapshot.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package Gradle_Promotion.buildTypes + +import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.schedule + +class PublishNightlySnapshot(uuid: String, branch: String, hour: Int) : PublishGradleDistribution( + branch = branch, + task = branch.promoteNightlyTaskName(), + triggerName = "ReadyforNightly" +) { + init { + this.uuid = uuid + id("Gradle_Promotion_${branch.capitalize()}Nightly") + name = "${branch.capitalize()} - Nightly Snapshot" + description = "Promotes the latest successful changes on '$branch' from Ready for Nightly as a new nightly snapshot" + + triggers { + schedule { + schedulingPolicy = daily { + this.hour = hour + } + triggerBuild = always() + withPendingChangesOnly = false + } + } + } +} diff --git a/.teamcity/Gradle_Promotion/buildTypes/PublishNightlySnapshotFromQuickFeedback.kt b/.teamcity/Gradle_Promotion/buildTypes/PublishNightlySnapshotFromQuickFeedback.kt new file mode 100644 index 0000000000000..229a40ebc4b51 --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/PublishNightlySnapshotFromQuickFeedback.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package Gradle_Promotion.buildTypes + +class PublishNightlySnapshotFromQuickFeedback(uuid: String, branch: String) : PublishGradleDistribution( + branch = branch, + task = branch.promoteNightlyTaskName(), + triggerName = "QuickFeedback" +) { + init { + this.uuid = uuid + id("Gradle_Promotion_${branch.capitalize()}SnapshotFromQuickFeedback") + name = "${branch.capitalize()} - Nightly Snapshot (from QuickFeedback)" + description = "Promotes the latest successful changes on '$branch' from Quick Feedback as a new nightly snapshot" + } +} diff --git a/.teamcity/Gradle_Promotion/buildTypes/PublishRelease.kt b/.teamcity/Gradle_Promotion/buildTypes/PublishRelease.kt new file mode 100644 index 0000000000000..4f72bc0de91b3 --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/PublishRelease.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package Gradle_Promotion.buildTypes + +import jetbrains.buildServer.configs.kotlin.v2018_2.ParameterDisplay + +abstract class PublishRelease(task: String, requiredConfirmationCode: String, branch: String = "release", init: PublishRelease.() -> Unit = {}) : PublishGradleDistribution( + branch = branch, + task = task, + triggerName = "ReadyforRelease", + gitUserEmail = "%gitUserEmail%", + gitUserName = "%gitUserName%", + extraParameters = "-PconfirmationCode=%confirmationCode%" +) { + init { + params { + text("gitUserEmail", "", label = "Git user.email Configuration", description = "Enter the git 'user.email' configuration to commit change under", display = ParameterDisplay.PROMPT, allowEmpty = true) + text("confirmationCode", "", label = "Confirmation Code", description = "Enter the value '$requiredConfirmationCode' (no quotes) to confirm the promotion", display = ParameterDisplay.PROMPT, allowEmpty = false) + text("gitUserName", "", label = "Git user.name Configuration", description = "Enter the git 'user.name' configuration to commit change under", display = ParameterDisplay.PROMPT, allowEmpty = true) + } + this.init() + } +} + +object PublishFinalRelease : PublishRelease(task = "promoteFinalRelease", requiredConfirmationCode = "final", init = { + uuid = "44e9390f-e46c-457e-aa18-31b020aef4de" + id("Gradle_Promotion_FinalRelease") + name = "Release - Final" + description = "Promotes the latest successful change on 'release' as a new release" +}) + +object PublishReleaseCandidate : PublishRelease(task = "promoteRc", requiredConfirmationCode = "rc", init = { + uuid = "5ed504bb-5ec3-46dc-a28a-e42a63ebbb31" + id("Gradle_Promotion_ReleaseCandidate") + name = "Release - Release Candidate" + description = "Promotes the latest successful change on 'release' as a new release candidate" +}) + +object PublishMilestone : PublishRelease(task = "promoteMilestone", requiredConfirmationCode = "milestone", init = { + uuid = "2ffb238a-08af-4f95-b863-9830d2bc3872" + id("Gradle_Promotion_Milestone") + name = "Release - Milestone" + description = "Promotes the latest successful change on 'release' as a new milestone" +}) diff --git a/.teamcity/Gradle_Promotion/buildTypes/StartReleaseCycle.kt b/.teamcity/Gradle_Promotion/buildTypes/StartReleaseCycle.kt new file mode 100644 index 0000000000000..15d18146df442 --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/StartReleaseCycle.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package Gradle_Promotion.buildTypes + +import common.gradleWrapper +import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId +import jetbrains.buildServer.configs.kotlin.v2018_2.ParameterDisplay + +object StartReleaseCycle : BasePromotionBuildType(vcsRoot = Gradle_Promotion.vcsRoots.Gradle_Promotion__master_) { + init { + uuid = "355487d7-45b9-4387-9fc5-713e7683e6d0" + id("Gradle_Promotion_StartReleaseCycle") + name = "Master - Start Release Cycle" + description = "Promotes a successful build on master as the start of a new release cycle on the release branch" + + artifactRules = """ + incoming-build-receipt/build-receipt.properties => incoming-build-receipt + """.trimIndent() + + params { + text("gitUserEmail", "", label = "Git user.email Configuration", description = "Enter the git 'user.email' configuration to commit change under", display = ParameterDisplay.PROMPT, allowEmpty = true) + text("confirmationCode", "", label = "Confirmation Code", description = "Enter the value 'startCycle' (no quotes) to confirm the promotion", display = ParameterDisplay.PROMPT, allowEmpty = false) + text("gitUserName", "", label = "Git user.name Configuration", description = "Enter the git 'user.name' configuration to commit change under", display = ParameterDisplay.PROMPT, allowEmpty = true) + } + + steps { + gradleWrapper { + name = "Promote" + tasks = "clean promoteStartReleaseCycle" + useGradleWrapper = true + gradleParams = """-PuseBuildReceipt -PconfirmationCode=%confirmationCode% "-PgitUserName=%gitUserName%" "-PgitUserEmail=%gitUserEmail%" -Igradle/buildScanInit.gradle""" + param("org.jfrog.artifactory.selectedDeployableServer.defaultModuleVersionConfiguration", "GLOBAL") + } + } + + dependencies { + artifacts(AbsoluteId("Gradle_Check_Stage_ReadyforNightly_Trigger")) { + buildRule = lastSuccessful("master") + cleanDestination = true + artifactRules = "build-receipt.properties => incoming-build-receipt/" + } + } + } +} diff --git a/.teamcity/Gradle_Promotion/buildTypes/StartReleaseCycleTest.kt b/.teamcity/Gradle_Promotion/buildTypes/StartReleaseCycleTest.kt new file mode 100644 index 0000000000000..5b31d8527fd3b --- /dev/null +++ b/.teamcity/Gradle_Promotion/buildTypes/StartReleaseCycleTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package Gradle_Promotion.buildTypes + +import common.gradleWrapper +import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.schedule +import jetbrains.buildServer.configs.kotlin.v2018_2.triggers.vcs + +object StartReleaseCycleTest : BasePromotionBuildType(vcsRoot = Gradle_Promotion.vcsRoots.Gradle_Promotion_GradlePromotionBranches, cleanCheckout = false) { + init { + uuid = "59823634-f79d-4c11-bbca-782957a7d65c" + id("Gradle_Promotion_AllBranchesStartReleaseCycleTest") + name = "Master - Start Release Cycle Test" + description = "Test for Start Release Cycle pipeline" + + steps { + gradleWrapper { + name = "PromoteTest" + tasks = "clean promoteStartReleaseCycle" + useGradleWrapper = true + gradleParams = "-PconfirmationCode=startCycle -Igradle/buildScanInit.gradle -PtestRun=1" + } + } + + triggers { + vcs { + branchFilter = "+:master" + } + schedule { + schedulingPolicy = daily { + hour = 3 + } + branchFilter = "+:master" + triggerBuild = always() + withPendingChangesOnly = false + } + } + } +} diff --git a/.teamcity/Gradle_Promotion/buildTypes/bt39.xml b/.teamcity/Gradle_Promotion/buildTypes/bt39.xml deleted file mode 100644 index 18b5cb6d4355a..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/bt39.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - Master - Nightly Snapshot - Promotes the latest successful change on 'master' as the new nightly - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/bt60.xml b/.teamcity/Gradle_Promotion/buildTypes/bt60.xml deleted file mode 100644 index 5c04be0a25954..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/bt60.xml +++ /dev/null @@ -1,44 +0,0 @@ - - - Release - Release Candidate - Promotes the latest successful change on 'release' as a new release candidate - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/buildTypes/bt61.xml b/.teamcity/Gradle_Promotion/buildTypes/bt61.xml deleted file mode 100644 index 986290cdca3a3..0000000000000 --- a/.teamcity/Gradle_Promotion/buildTypes/bt61.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - Release - Release Nightly Snapshot - Deploys the latest successful change on 'release' as a new release nightly snapshot - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/pluginData/plugin-settings.xml b/.teamcity/Gradle_Promotion/pluginData/plugin-settings.xml index ade07a22aed3b..677e924ad523a 100644 --- a/.teamcity/Gradle_Promotion/pluginData/plugin-settings.xml +++ b/.teamcity/Gradle_Promotion/pluginData/plugin-settings.xml @@ -1,20 +1,3 @@ - - - - - - - - - - - - - - - - - - + diff --git a/.teamcity/Gradle_Promotion/project-config.xml b/.teamcity/Gradle_Promotion/project-config.xml deleted file mode 100644 index eff5d262d249e..0000000000000 --- a/.teamcity/Gradle_Promotion/project-config.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - Promotion - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_AgentTest/settings.kts b/.teamcity/Gradle_Promotion/settings.kts similarity index 50% rename from .teamcity/Gradle_AgentTest/settings.kts rename to .teamcity/Gradle_Promotion/settings.kts index 51ff91438f3b8..c7191ef3ac88f 100644 --- a/.teamcity/Gradle_AgentTest/settings.kts +++ b/.teamcity/Gradle_Promotion/settings.kts @@ -1,17 +1,6 @@ -package Gradle_AgentTest - -import jetbrains.buildServer.configs.kotlin.v2018_2.project -import jetbrains.buildServer.configs.kotlin.v2018_2.version -import model.CIBuildModel -import model.JvmVersion -import model.NoBuildCache -import model.OS -import model.SpecificBuild -import model.Stage -import model.StageNames -import model.TestCoverage -import model.TestType -import projects.RootProject +package Gradle_Promotion + +import jetbrains.buildServer.configs.kotlin.v2018_2.* /* The settings script is an entry point for defining a single @@ -43,20 +32,4 @@ the 'Debug' option is available in the context menu for the task. */ version = "2018.2" -val buildModel = CIBuildModel( - projectPrefix = "Gradle_AgentTest_", - rootProjectName = "Test Build Agents", - masterAndReleaseBranches = listOf("master"), - parentBuildCache = NoBuildCache, - childBuildCache = NoBuildCache, - tagBuilds = false, - publishStatusToGitHub = false, - buildScanTags = listOf("AgentTest"), - stages = listOf( - Stage(StageNames.QUICK_FEEDBACK_LINUX_ONLY, - runsIndependent = true, - specificBuilds = listOf(SpecificBuild.CompileAll, SpecificBuild.SanityCheck), - functionalTests = listOf(TestCoverage(TestType.quick, OS.linux, JvmVersion.java8))) - ) -) -project(RootProject(buildModel)) +project(Gradle_Promotion.Project) \ No newline at end of file diff --git a/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion_GradlePromotionBranches.kt b/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion_GradlePromotionBranches.kt new file mode 100644 index 0000000000000..22443be0a5387 --- /dev/null +++ b/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion_GradlePromotionBranches.kt @@ -0,0 +1,16 @@ +package Gradle_Promotion.vcsRoots + +import jetbrains.buildServer.configs.kotlin.v2018_2.vcs.GitVcsRoot + +object Gradle_Promotion_GradlePromotionBranches : GitVcsRoot({ + uuid = "e4bc6ac6-ab3f-4459-b4c4-7d6ba6e2cbf6" + name = "Gradle Promotion Branches" + url = "https://github.com/gradle/gradle-promote.git" + branchSpec = "+:refs/heads/*" + agentGitPath = "%env.TEAMCITY_GIT_PATH%" + useMirrors = false + authMethod = password { + userName = "gradlewaregitbot" + password = "credentialsJSON:5306bfc7-041e-46e8-8d61-1d49424e7b04" + } +}) diff --git a/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion_GradlePromotionBranches.xml b/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion_GradlePromotionBranches.xml deleted file mode 100644 index fb3f3ab812928..0000000000000 --- a/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion_GradlePromotionBranches.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - Gradle Promotion Branches - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion__master_.kt b/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion__master_.kt new file mode 100644 index 0000000000000..e8755b4c15c14 --- /dev/null +++ b/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion__master_.kt @@ -0,0 +1,16 @@ +package Gradle_Promotion.vcsRoots + +import jetbrains.buildServer.configs.kotlin.v2018_2.vcs.GitVcsRoot + +object Gradle_Promotion__master_ : GitVcsRoot({ + uuid = "0974a0e7-3f2f-4c3f-a185-aded6ac045ff" + name = "Gradle Promotion" + url = "https://github.com/gradle/gradle-promote.git" + branch = "master" + agentGitPath = "%env.TEAMCITY_GIT_PATH%" + useMirrors = false + authMethod = password { + userName = "gradlewaregitbot" + password = "credentialsJSON:69f980df-e4d1-4622-9f0f-5be59df88a04" + } +}) diff --git a/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion__master_.xml b/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion__master_.xml deleted file mode 100644 index 18f29ceaa6a6d..0000000000000 --- a/.teamcity/Gradle_Promotion/vcsRoots/Gradle_Promotion__master_.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - Gradle Promotion - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Util/Project.kt b/.teamcity/Gradle_Util/Project.kt new file mode 100644 index 0000000000000..068cdc5088e6a --- /dev/null +++ b/.teamcity/Gradle_Util/Project.kt @@ -0,0 +1,29 @@ +package Gradle_Util + +import Gradle_Util.buildTypes.* +import jetbrains.buildServer.configs.kotlin.v2018_2.* +import jetbrains.buildServer.configs.kotlin.v2018_2.Project +import jetbrains.buildServer.configs.kotlin.v2018_2.projectFeatures.VersionedSettings +import jetbrains.buildServer.configs.kotlin.v2018_2.projectFeatures.versionedSettings + +object Project : Project({ + uuid = "077cff89-d1d3-407b-acc0-88446a99dec7" + id("Gradle_Util") + parentId("Gradle") + name = "Util" + + buildType(Gradle_Util_AdHocFunctionalTestWindows) + buildType(Gradle_Util_AdHocFunctionalTestLinux) + + features { + versionedSettings { + id = "PROJECT_EXT_16" + mode = VersionedSettings.Mode.ENABLED + buildSettingsMode = VersionedSettings.BuildSettingsMode.PREFER_SETTINGS_FROM_VCS + rootExtId = "Gradle_Branches_VersionedSettings" + showChanges = false + settingsFormat = VersionedSettings.Format.KOTLIN + storeSecureParamsOutsideOfVcs = true + } + } +}) diff --git a/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestLinux.kt b/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestLinux.kt new file mode 100644 index 0000000000000..0cc845bbc2703 --- /dev/null +++ b/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestLinux.kt @@ -0,0 +1,74 @@ +package Gradle_Util.buildTypes + +import common.Os +import common.checkCleanM2 +import common.gradleWrapper +import common.verifyTestFilesCleanup +import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2018_2.CheckoutMode +import jetbrains.buildServer.configs.kotlin.v2018_2.FailureAction +import jetbrains.buildServer.configs.kotlin.v2018_2.ParameterDisplay + +object Gradle_Util_AdHocFunctionalTestLinux : BuildType({ + uuid = "5d59fee9-be42-4f6d-9e0b-fe103e0d2765" + name = "AdHoc Functional Test - Linux" + + artifactRules = """ + build/report-* => . + buildSrc/build/report-* => . + subprojects/*/build/tmp/test files/** => test-files + build/errorLogs/** => errorLogs + """.trimIndent() + + params { + param("maxParallelForks", "4") + select("subproject", "", display = ParameterDisplay.PROMPT, + options = listOf("announce", "antlr", "baseServices", "baseServicesGroovy", "buildCache", "buildCacheHttp", "buildComparison", "buildInit", "buildScanPerformance", "cli", "codeQuality", "compositeBuilds", "core", "coreApi", "dependencyManagement", "diagnostics", "distributions", "docs", "ear", "ide", "ideNative", "idePlay", "installationBeacon", "integTest", "internalAndroidPerformanceTesting", "internalIntegTesting", "internalPerformanceTesting", "internalTesting", "ivy", "jacoco", "javascript", "jvmServices", "languageGroovy", "languageJava", "languageJvm", "languageNative", "languageScala", "launcher", "logging", "maven", "messaging", "modelCore", "modelGroovy", "native", "osgi", "performance", "persistentCache", "platformBase", "platformJvm", "platformNative", "platformPlay", "pluginDevelopment", "pluginUse", "plugins", "processServices", "publish", "reporting", "resources", "resourcesGcs", "resourcesHttp", "resourcesS3", "resourcesSftp", "runtimeApiInfo", "scala", "signing", "smokeTest", "soak", "testKit", "testingBase", "testingJvm", "testingNative", "toolingApi", "toolingApiBuilders", "workers", "wrapper")) + param("env.ANDROID_HOME", "/opt/android/sdk") + select("buildType", "", display = ParameterDisplay.PROMPT, + options = listOf("quickTest", "platformTest", "crossVersionTest", "quickFeedbackCrossVersionTest", "parallelTest", "noDaemonTest", "java9SmokeTest")) + } + + vcs { + root(AbsoluteId("Gradle_Branches_GradlePersonalBranches")) + + checkoutMode = CheckoutMode.ON_AGENT + buildDefaultBranch = false + } + + steps { + gradleWrapper { + name = "GRADLE_RUNNER" + tasks = "clean %subproject%:%buildType%" + gradleParams = "-PmaxParallelForks=%maxParallelForks% -s --no-daemon --continue -I ./gradle/init-scripts/build-scan.init.gradle.kts" + } + checkCleanM2(Os.linux) + verifyTestFilesCleanup() + } + + failureConditions { + executionTimeoutMin = 180 + } + + dependencies { + dependency(AbsoluteId("Gradle_Check_BuildDistributions")) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + + artifacts { + cleanDestination = true + artifactRules = """ + distributions/*-all.zip => incoming-distributions + build-receipt.properties => incoming-distributions + """.trimIndent() + } + } + } + + requirements { + contains("teamcity.agent.jvm.os.name", "Linux") + } +}) diff --git a/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestLinux.xml b/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestLinux.xml deleted file mode 100644 index 1267633ad724c..0000000000000 --- a/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestLinux.xml +++ /dev/null @@ -1,101 +0,0 @@ - - - AdHoc Functional Test - Linux - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestWindows.kt b/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestWindows.kt new file mode 100644 index 0000000000000..e4d7dbee84e60 --- /dev/null +++ b/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestWindows.kt @@ -0,0 +1,74 @@ +package Gradle_Util.buildTypes + +import common.Os +import common.checkCleanM2 +import common.gradleWrapper +import common.verifyTestFilesCleanup +import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2018_2.CheckoutMode +import jetbrains.buildServer.configs.kotlin.v2018_2.FailureAction +import jetbrains.buildServer.configs.kotlin.v2018_2.ParameterDisplay + +object Gradle_Util_AdHocFunctionalTestWindows : BuildType({ + uuid = "944be583-446e-4ecc-b2f2-15f04dd0cfe9" + name = "AdHoc Functional Test - Windows" + + artifactRules = """ + build/report-* => . + buildSrc/build/report-* => . + subprojects/*/build/tmp/test files/** => test-files + build/errorLogs/** => errorLogs + """.trimIndent() + + params { + param("maxParallelForks", "4") + select("subproject", "", display = ParameterDisplay.PROMPT, + options = listOf("announce", "antlr", "baseServices", "baseServicesGroovy", "buildCache", "buildCacheHttp", "buildComparison", "buildInit", "buildScanPerformance", "cli", "codeQuality", "compositeBuilds", "core", "coreApi", "dependencyManagement", "diagnostics", "distributions", "docs", "ear", "ide", "ideNative", "idePlay", "installationBeacon", "integTest", "internalAndroidPerformanceTesting", "internalIntegTesting", "internalPerformanceTesting", "internalTesting", "ivy", "jacoco", "javascript", "jvmServices", "languageGroovy", "languageJava", "languageJvm", "languageNative", "languageScala", "launcher", "logging", "maven", "messaging", "modelCore", "modelGroovy", "native", "osgi", "performance", "persistentCache", "platformBase", "platformJvm", "platformNative", "platformPlay", "pluginDevelopment", "pluginUse", "plugins", "processServices", "publish", "reporting", "resources", "resourcesGcs", "resourcesHttp", "resourcesS3", "resourcesSftp", "runtimeApiInfo", "scala", "signing", "smokeTest", "soak", "testKit", "testingBase", "testingJvm", "testingNative", "toolingApi", "toolingApiBuilders", "workers", "wrapper")) + param("env.JAVA_HOME", "%windows.java9.oracle.64bit%") + select("buildType", "", display = ParameterDisplay.PROMPT, + options = listOf("quickTest", "platformTest", "crossVersionTest", "quickFeedbackCrossVersionTest", "parallelTest", "noDaemonTest", "java9SmokeTest")) + } + + vcs { + root(AbsoluteId("Gradle_Branches_GradlePersonalBranches")) + + checkoutMode = CheckoutMode.ON_AGENT + buildDefaultBranch = false + } + + steps { + gradleWrapper { + name = "GRADLE_RUNNER" + tasks = "clean %subproject%:%buildType%" + gradleParams = """-PmaxParallelForks=%maxParallelForks% -s --no-daemon --continue -I ./gradle/init-scripts/build-scan.init.gradle.kts "-PtestJavaHome=%windows.java8.oracle.64bit%"""" + } + checkCleanM2(Os.windows) + verifyTestFilesCleanup() + } + + failureConditions { + executionTimeoutMin = 180 + } + + dependencies { + dependency(AbsoluteId("Gradle_Check_BuildDistributions")) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + + artifacts { + cleanDestination = true + artifactRules = """ + distributions/*-all.zip => incoming-distributions + build-receipt.properties => incoming-distributions + """.trimIndent() + } + } + } + + requirements { + contains("teamcity.agent.jvm.os.name", "Windows") + } +}) diff --git a/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestWindows.xml b/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestWindows.xml deleted file mode 100644 index 96f9f9de3fb2b..0000000000000 --- a/.teamcity/Gradle_Util/buildTypes/Gradle_Util_AdHocFunctionalTestWindows.xml +++ /dev/null @@ -1,97 +0,0 @@ - - - AdHoc Functional Test - Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Util/pluginData/plugin-settings.xml b/.teamcity/Gradle_Util/pluginData/plugin-settings.xml deleted file mode 100644 index 677e924ad523a..0000000000000 --- a/.teamcity/Gradle_Util/pluginData/plugin-settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/.teamcity/Gradle_Util/project-config.xml b/.teamcity/Gradle_Util/project-config.xml deleted file mode 100644 index 5cfe45692ba2f..0000000000000 --- a/.teamcity/Gradle_Util/project-config.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - Util - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Util/settings.kts b/.teamcity/Gradle_Util/settings.kts new file mode 100644 index 0000000000000..45adc3598e791 --- /dev/null +++ b/.teamcity/Gradle_Util/settings.kts @@ -0,0 +1,35 @@ +package Gradle_Util + +import jetbrains.buildServer.configs.kotlin.v2018_2.* + +/* +The settings script is an entry point for defining a single +TeamCity project. TeamCity looks for the 'settings.kts' file in a +project directory and runs it if it's found, so the script name +shouldn't be changed and its package should be the same as the +project's id. + +The script should contain a single call to the project() function +with a Project instance or an init function as an argument. + +VcsRoots, BuildTypes, and Templates of this project must be +registered inside project using the vcsRoot(), buildType(), and +template() methods respectively. + +Subprojects can be defined either in their own settings.kts or by +calling the subProjects() method in this project. + +To debug settings scripts in command-line, run the + + mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate + +command and attach your debugger to the port 8000. + +To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View -> +Tool Windows -> Maven Projects), find the generate task +node (Plugins -> teamcity-configs -> teamcity-configs:generate), +the 'Debug' option is available in the context menu for the task. +*/ + +version = "2018.2" +project(Gradle_Util.Project) \ No newline at end of file diff --git a/.teamcity/Gradle_Util_Performance/Project.kt b/.teamcity/Gradle_Util_Performance/Project.kt new file mode 100644 index 0000000000000..940095e88c6b4 --- /dev/null +++ b/.teamcity/Gradle_Util_Performance/Project.kt @@ -0,0 +1,15 @@ +package Gradle_Util_Performance + +import Gradle_Util_Performance.buildTypes.AdHocPerformanceScenarioLinux +import Gradle_Util_Performance.buildTypes.AdHocPerformanceTestCoordinatorLinux +import jetbrains.buildServer.configs.kotlin.v2018_2.Project + +object Project : Project({ + uuid = "fdc4f15a-e253-4744-a1b3-bcac37b18189" + id("Gradle_Util_Performance") + parentId("Gradle_Util") + name = "Performance" + + buildType(AdHocPerformanceScenarioLinux) + buildType(AdHocPerformanceTestCoordinatorLinux) +}) diff --git a/.teamcity/Gradle_Util_Performance/buildTypes/AdHocPerformanceScenarioLinux.kt b/.teamcity/Gradle_Util_Performance/buildTypes/AdHocPerformanceScenarioLinux.kt new file mode 100644 index 0000000000000..20a7fdf18082f --- /dev/null +++ b/.teamcity/Gradle_Util_Performance/buildTypes/AdHocPerformanceScenarioLinux.kt @@ -0,0 +1,53 @@ +package Gradle_Util_Performance.buildTypes + +import common.Os +import common.applyPerformanceTestSettings +import common.buildToolGradleParameters +import common.builtInRemoteBuildCacheNode +import common.checkCleanM2 +import common.gradleWrapper +import common.performanceTestCommandLine +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2018_2.ParameterDisplay + +object AdHocPerformanceScenarioLinux : BuildType({ + uuid = "a3183d81-e07d-475c-8ef6-04ed60bf4053" + name = "AdHoc Performance Scenario - Linux" + id("Gradle_Util_Performance_AdHocPerformanceScenarioLinux") + + applyPerformanceTestSettings(timeout = 420) + artifactRules = """ + subprojects/*/build/test-results-*.zip => results + subprojects/*/build/tmp/**/log.txt => failure-logs + """.trimIndent() + + params { + text("baselines", "defaults", display = ParameterDisplay.PROMPT, allowEmpty = false) + text("templates", "", display = ParameterDisplay.PROMPT, allowEmpty = false) + param("channel", "adhoc") + param("checks", "all") + text("runs", "10", display = ParameterDisplay.PROMPT, allowEmpty = false) + text("warmups", "3", display = ParameterDisplay.PROMPT, allowEmpty = false) + text("scenario", "", display = ParameterDisplay.PROMPT, allowEmpty = false) + param("flamegraphs", "--flamegraphs true") + param("env.FG_HOME_DIR", "/opt/FlameGraph") + param("additional.gradle.parameters", "") + + param("env.ANDROID_HOME", "/opt/android/sdk") + param("env.PATH", "%env.PATH%:/opt/swift/latest/usr/bin") + param("env.HP_HOME_DIR", "/opt/honest-profiler") + } + + steps { + gradleWrapper { + name = "GRADLE_RUNNER" + gradleParams = ( + performanceTestCommandLine("%templates% performance:performanceAdHocTest", "%baselines%", + """--scenarios "%scenario%" --warmups %warmups% --runs %runs% --checks %checks% --channel %channel% %flamegraphs% %additional.gradle.parameters%""") + + buildToolGradleParameters() + + builtInRemoteBuildCacheNode.gradleParameters(Os.linux) + ).joinToString(separator = " ") + } + checkCleanM2() + } +}) diff --git a/.teamcity/Gradle_Util_Performance/buildTypes/AdHocPerformanceTestCoordinatorLinux.kt b/.teamcity/Gradle_Util_Performance/buildTypes/AdHocPerformanceTestCoordinatorLinux.kt new file mode 100644 index 0000000000000..d5d78e47c647b --- /dev/null +++ b/.teamcity/Gradle_Util_Performance/buildTypes/AdHocPerformanceTestCoordinatorLinux.kt @@ -0,0 +1,44 @@ +package Gradle_Util_Performance.buildTypes + +import common.Os +import common.applyPerformanceTestSettings +import common.buildToolGradleParameters +import common.builtInRemoteBuildCacheNode +import common.checkCleanM2 +import common.compileAllDependency +import common.distributedPerformanceTestParameters +import common.gradleWrapper +import common.performanceTestCommandLine +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType + +object AdHocPerformanceTestCoordinatorLinux : BuildType({ + uuid = "a28ced77-77d1-41fd-bc3b-fe9c9016bf7b" + id("Gradle_Util_Performance_PerformanceTestCoordinatorLinux") + name = "AdHoc Performance Test Coordinator - Linux" + + applyPerformanceTestSettings(os = Os.linux, timeout = 420) + + maxRunningBuilds = 2 + + params { + param("performance.baselines", "defaults") + } + + steps { + gradleWrapper { + name = "GRADLE_RUNNER" + tasks = "" + gradleParams = ( + buildToolGradleParameters(isContinue = false) + + performanceTestCommandLine(task = "distributedPerformanceTests", baselines = "%performance.baselines%") + + distributedPerformanceTestParameters("Gradle_Check_IndividualPerformanceScenarioWorkersLinux") + + builtInRemoteBuildCacheNode.gradleParameters(Os.linux) + ).joinToString(separator = " ") + } + checkCleanM2(Os.linux) + } + + dependencies { + compileAllDependency() + } +}) diff --git a/.teamcity/Gradle_Util_Performance/buildTypes/Gradle_Util_Performance_AdHocPerformanceScenarioLinux.xml b/.teamcity/Gradle_Util_Performance/buildTypes/Gradle_Util_Performance_AdHocPerformanceScenarioLinux.xml deleted file mode 100644 index 880e8d5c6b58d..0000000000000 --- a/.teamcity/Gradle_Util_Performance/buildTypes/Gradle_Util_Performance_AdHocPerformanceScenarioLinux.xml +++ /dev/null @@ -1,67 +0,0 @@ - - - AdHoc Performance Scenario - Linux - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Util_Performance/buildTypes/Gradle_Util_Performance_PerformanceTestCoordinatorLinux.xml b/.teamcity/Gradle_Util_Performance/buildTypes/Gradle_Util_Performance_PerformanceTestCoordinatorLinux.xml deleted file mode 100644 index 36adaa2ce2a51..0000000000000 --- a/.teamcity/Gradle_Util_Performance/buildTypes/Gradle_Util_Performance_PerformanceTestCoordinatorLinux.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - AdHoc Performance Test Coordinator - Linux - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.teamcity/Gradle_Util_Performance/pluginData/plugin-settings.xml b/.teamcity/Gradle_Util_Performance/pluginData/plugin-settings.xml deleted file mode 100644 index 677e924ad523a..0000000000000 --- a/.teamcity/Gradle_Util_Performance/pluginData/plugin-settings.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/.teamcity/Gradle_Util_Performance/project-config.xml b/.teamcity/Gradle_Util_Performance/project-config.xml deleted file mode 100644 index 842ef873cfae2..0000000000000 --- a/.teamcity/Gradle_Util_Performance/project-config.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - Performance - - - - diff --git a/.teamcity/Gradle_Util_Performance/settings.kts b/.teamcity/Gradle_Util_Performance/settings.kts new file mode 100644 index 0000000000000..b136aefa9183e --- /dev/null +++ b/.teamcity/Gradle_Util_Performance/settings.kts @@ -0,0 +1,35 @@ +package Gradle_Util_Performance + +import jetbrains.buildServer.configs.kotlin.v2018_2.* + +/* +The settings script is an entry point for defining a single +TeamCity project. TeamCity looks for the 'settings.kts' file in a +project directory and runs it if it's found, so the script name +shouldn't be changed and its package should be the same as the +project's id. + +The script should contain a single call to the project() function +with a Project instance or an init function as an argument. + +VcsRoots, BuildTypes, and Templates of this project must be +registered inside project using the vcsRoot(), buildType(), and +template() methods respectively. + +Subprojects can be defined either in their own settings.kts or by +calling the subProjects() method in this project. + +To debug settings scripts in command-line, run the + + mvnDebug org.jetbrains.teamcity:teamcity-configs-maven-plugin:generate + +command and attach your debugger to the port 8000. + +To debug in IntelliJ Idea, open the 'Maven Projects' tool window (View -> +Tool Windows -> Maven Projects), find the generate task +node (Plugins -> teamcity-configs -> teamcity-configs:generate), +the 'Debug' option is available in the context menu for the task. +*/ + +version = "2018.2" +project(Gradle_Util_Performance.Project) \ No newline at end of file diff --git a/.teamcity/common/BuildCache.kt b/.teamcity/common/BuildCache.kt new file mode 100644 index 0000000000000..f03b7f039a25d --- /dev/null +++ b/.teamcity/common/BuildCache.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package common + +interface BuildCache { + fun gradleParameters(os: Os): List +} + +data class RemoteBuildCache(val url: String, val username: String = "%gradle.cache.remote.username%", val password: String = "%gradle.cache.remote.password%") : BuildCache { + override fun gradleParameters(os: Os): List { + return listOf("--build-cache", + os.escapeKeyValuePair("-Dgradle.cache.remote.url", url), + os.escapeKeyValuePair("-Dgradle.cache.remote.username", username), + os.escapeKeyValuePair("-Dgradle.cache.remote.password", password) + ) + } +} + +val builtInRemoteBuildCacheNode = RemoteBuildCache("%gradle.cache.remote.url%") + +object NoBuildCache : BuildCache { + override fun gradleParameters(os: Os): List { + return emptyList() + } +} + +private +fun Os.escapeKeyValuePair(key: String, value: String) = if (this == Os.windows) """$key="$value"""" else """"$key=$value"""" diff --git a/.teamcity/common/JvmCategory.kt b/.teamcity/common/JvmCategory.kt new file mode 100644 index 0000000000000..2280ef02bb942 --- /dev/null +++ b/.teamcity/common/JvmCategory.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package common + +enum class JvmCategory(val vendor: JvmVendor, val version: JvmVersion) { + MIN_VERSION(JvmVendor.oracle, JvmVersion.java8), + MAX_VERSION(JvmVendor.openjdk, JvmVersion.java12), + EXPERIMENTAL_VERSION(JvmVendor.openjdk, JvmVersion.java12) +} diff --git a/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/command/BuildCacheLoadListener.java b/.teamcity/common/JvmVendor.kt similarity index 72% rename from subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/command/BuildCacheLoadListener.java rename to .teamcity/common/JvmVendor.kt index a6aa2ed88071e..528237a4e0088 100644 --- a/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/command/BuildCacheLoadListener.java +++ b/.teamcity/common/JvmVendor.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,8 @@ * limitations under the License. */ -package org.gradle.caching.internal.command; +package common -public interface BuildCacheLoadListener { - void beforeLoad(); - void afterLoadFailedAndWasCleanedUp(Throwable error); +enum class JvmVendor { + oracle, ibm, openjdk } diff --git a/.teamcity/common/JvmVersion.kt b/.teamcity/common/JvmVersion.kt new file mode 100644 index 0000000000000..26ddb0e688a10 --- /dev/null +++ b/.teamcity/common/JvmVersion.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package common + +enum class JvmVersion { + java8, java9, java10, java11, java12 +} diff --git a/.teamcity/common/Os.kt b/.teamcity/common/Os.kt new file mode 100644 index 0000000000000..a406b7a2fad04 --- /dev/null +++ b/.teamcity/common/Os.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package common + +enum class Os(val agentRequirement: String, val ignoredSubprojects: List = emptyList()) { + linux("Linux"), windows("Windows"), macos("Mac", listOf("integTest", "native", "plugins", "resources", "scala", "workers", "wrapper", "platformPlay")) +} diff --git a/.teamcity/common/extensions.kt b/.teamcity/common/extensions.kt new file mode 100644 index 0000000000000..7fd339f0f6b12 --- /dev/null +++ b/.teamcity/common/extensions.kt @@ -0,0 +1,128 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package common + +import configurations.m2CleanScriptUnixLike +import configurations.m2CleanScriptWindows +import jetbrains.buildServer.configs.kotlin.v2018_2.AbsoluteId +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildStep +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildSteps +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType +import jetbrains.buildServer.configs.kotlin.v2018_2.CheckoutMode +import jetbrains.buildServer.configs.kotlin.v2018_2.Dependencies +import jetbrains.buildServer.configs.kotlin.v2018_2.FailureAction +import jetbrains.buildServer.configs.kotlin.v2018_2.Requirements +import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.GradleBuildStep +import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.script + +fun BuildSteps.customGradle(init: GradleBuildStep.() -> Unit, custom: GradleBuildStep.() -> Unit): GradleBuildStep = + GradleBuildStep(init) + .apply(custom) + .also { step(it) } + +/** + * Adds a [Gradle build step](https://confluence.jetbrains.com/display/TCDL/Gradle) + * that runs with the Gradle wrapper. + * + * @see GradleBuildStep + */ +fun BuildSteps.gradleWrapper(init: GradleBuildStep.() -> Unit): GradleBuildStep = + customGradle(init) { + useGradleWrapper = true + if (buildFile == null) { + buildFile = "" // Let Gradle detect the build script + } + } + +fun Requirements.requiresOs(os: Os) { + contains("teamcity.agent.jvm.os.name", os.agentRequirement) +} + +fun BuildType.applyDefaultSettings(os: Os = Os.linux, timeout: Int = 30, vcsRoot: String = "Gradle_Branches_GradlePersonalBranches") { + artifactRules = """ + build/report-* => . + buildSrc/build/report-* => . + subprojects/*/build/tmp/test files/** => test-files + build/errorLogs/** => errorLogs + build/reports/incubation/** => incubation-reports + """.trimIndent() + + vcs { + root(AbsoluteId(vcsRoot)) + checkoutMode = CheckoutMode.ON_AGENT + buildDefaultBranch = !vcsRoot.contains("Branches") + } + + requirements { + requiresOs(os) + } + + failureConditions { + executionTimeoutMin = timeout + } + + if (os == Os.linux || os == Os.macos) { + params { + param("env.LC_ALL", "en_US.UTF-8") + } + } +} + +fun BuildSteps.checkCleanM2(os: Os = Os.linux) { + script { + name = "CHECK_CLEAN_M2" + executionMode = BuildStep.ExecutionMode.ALWAYS + scriptContent = if (os == Os.windows) m2CleanScriptWindows else m2CleanScriptUnixLike + } +} + +fun buildToolGradleParameters(daemon: Boolean = true, isContinue: Boolean = true): List = + listOf( + "-PmaxParallelForks=%maxParallelForks%", + "-s", + if (daemon) "--daemon" else "--no-daemon", + if (isContinue) "--continue" else "", + """-I "%teamcity.build.checkoutDir%/gradle/init-scripts/build-scan.init.gradle.kts"""", + "-Dorg.gradle.internal.tasks.createops", + "-Dorg.gradle.internal.plugins.portal.url.override=%gradle.plugins.portal.url%" + ) + +fun buildToolParametersString(daemon: Boolean = true) = buildToolGradleParameters(daemon).joinToString(separator = " ") + +fun Dependencies.compileAllDependency(compileAllId: String = "Gradle_Check_CompileAll") { + // Compile All has to succeed before anything else is started + dependency(AbsoluteId(compileAllId)) { + snapshot { + onDependencyFailure = FailureAction.CANCEL + onDependencyCancel = FailureAction.CANCEL + } + } + // Get the build receipt from sanity check to reuse the timestamp + artifacts(AbsoluteId(compileAllId)) { + id = "ARTIFACT_DEPENDENCY_$compileAllId" + cleanDestination = true + artifactRules = "build-receipt.properties => incoming-distributions" + } +} + +fun BuildSteps.verifyTestFilesCleanup(daemon: Boolean = true) { + gradleWrapper { + name = "VERIFY_TEST_FILES_CLEANUP" + tasks = "verifyTestFilesCleanup" + gradleParams = buildToolParametersString(daemon) + } +} diff --git a/.teamcity/common/performance-test-extensions.kt b/.teamcity/common/performance-test-extensions.kt new file mode 100644 index 0000000000000..ec2e04cc730ea --- /dev/null +++ b/.teamcity/common/performance-test-extensions.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package common + +import configurations.buildJavaHome +import configurations.coordinatorPerformanceTestJavaHome +import jetbrains.buildServer.configs.kotlin.v2018_2.BuildType + +fun BuildType.applyPerformanceTestSettings(os: Os = Os.linux, timeout: Int = 30) { + applyDefaultSettings(os = os, timeout = timeout) + artifactRules = """ + build/report-*-performance-tests.zip => . + """.trimIndent() + detectHangingBuilds = false + requirements { + doesNotContain("teamcity.agent.name", "ec2") + } + params { + param("env.GRADLE_OPTS", "-Xmx1536m -XX:MaxPermSize=384m") + param("env.JAVA_HOME", buildJavaHome) + param("env.BUILD_BRANCH", "%teamcity.build.branch%") + param("performance.db.url", "jdbc:h2:ssl://dev61.gradle.org:9092") + param("performance.db.username", "tcagent") + param("TC_USERNAME", "TeamcityRestBot") + } +} + +fun performanceTestCommandLine(task: String, baselines: String, extraParameters: String = "", testJavaHome: String = coordinatorPerformanceTestJavaHome) = listOf( + "clean $task --baselines $baselines $extraParameters", + "-x prepareSamples", + "-Porg.gradle.performance.branchName=%teamcity.build.branch%", + "-Porg.gradle.performance.db.url=%performance.db.url% -Porg.gradle.performance.db.username=%performance.db.username% -Porg.gradle.performance.db.password=%performance.db.password.tcagent%", + "-PteamCityUsername=%TC_USERNAME% -PteamCityPassword=%teamcity.password.restbot%", + "-PtestJavaHome=$testJavaHome" +) + +fun distributedPerformanceTestParameters(workerId: String = "Gradle_Check_IndividualPerformanceScenarioWorkersLinux") = listOf( + "-Porg.gradle.performance.buildTypeId=${workerId} -Porg.gradle.performance.workerTestTaskName=fullPerformanceTest -Porg.gradle.performance.coordinatorBuildId=%teamcity.build.id%" +) + diff --git a/.teamcityTest/Gradle_Check_Tests/ApplyDefaultConfigurationTest.kt b/.teamcityTest/Gradle_Check_Tests/ApplyDefaultConfigurationTest.kt index a1585bdc74190..ebfcf23fdccdf 100644 --- a/.teamcityTest/Gradle_Check_Tests/ApplyDefaultConfigurationTest.kt +++ b/.teamcityTest/Gradle_Check_Tests/ApplyDefaultConfigurationTest.kt @@ -1,3 +1,4 @@ +import common.Os import configurations.BaseGradleBuildType import configurations.applyDefaults import configurations.applyTestDefaults @@ -8,9 +9,7 @@ import io.mockk.mockk import io.mockk.slot import jetbrains.buildServer.configs.kotlin.v2018_2.BuildStep import jetbrains.buildServer.configs.kotlin.v2018_2.BuildSteps -import jetbrains.buildServer.configs.kotlin.v2018_2.FailureConditions import jetbrains.buildServer.configs.kotlin.v2018_2.buildSteps.GradleBuildStep -import model.OS import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -63,8 +62,7 @@ class ApplyDefaultConfigurationTest { assertEquals(listOf( "GRADLE_RUNNER", "CHECK_CLEAN_M2", - "VERIFY_TEST_FILES_CLEANUP", - "TAG_BUILD" + "VERIFY_TEST_FILES_CLEANUP" ), steps.items.map(BuildStep::name)) assertEquals(expectedRunnerParam(), getGradleStep("GRADLE_RUNNER").gradleParams) } @@ -83,8 +81,7 @@ class ApplyDefaultConfigurationTest { "GRADLE_RUNNER", "GRADLE_RERUNNER", "CHECK_CLEAN_M2", - "VERIFY_TEST_FILES_CLEANUP", - "TAG_BUILD" + "VERIFY_TEST_FILES_CLEANUP" ), steps.items.map(BuildStep::name)) verifyGradleRunnerParams(extraParameters, daemon, expectedDaemonParam) } @@ -97,7 +94,7 @@ class ApplyDefaultConfigurationTest { "'' , false, '--no-daemon'" ]) fun `can apply defaults to windows test configurations`(extraParameters: String, daemon: Boolean, expectedDaemonParam: String) { - applyTestDefaults(buildModel, buildType, "myTask", os = OS.windows, extraParameters = extraParameters, daemon = daemon) + applyTestDefaults(buildModel, buildType, "myTask", os = Os.windows, extraParameters = extraParameters, daemon = daemon) assertEquals(listOf( "GRADLE_RUNNER", @@ -105,8 +102,7 @@ class ApplyDefaultConfigurationTest { "GRADLE_RERUNNER", "KILL_PROCESSES_STARTED_BY_GRADLE_RERUN", "CHECK_CLEAN_M2", - "VERIFY_TEST_FILES_CLEANUP", - "TAG_BUILD" + "VERIFY_TEST_FILES_CLEANUP" ), steps.items.map(BuildStep::name)) verifyGradleRunnerParams(extraParameters, daemon, expectedDaemonParam) } @@ -117,9 +113,9 @@ class ApplyDefaultConfigurationTest { assertEquals(BuildStep.ExecutionMode.RUN_ON_FAILURE, getGradleStep("GRADLE_RERUNNER").executionMode) assertEquals(expectedRunnerParam(expectedDaemonParam, extraParameters), getGradleStep("GRADLE_RUNNER").gradleParams) - assertEquals(expectedRunnerParam(expectedDaemonParam, extraParameters) + " -PonlyPreviousFailedTestClasses=true", getGradleStep("GRADLE_RERUNNER").gradleParams) + assertEquals(expectedRunnerParam(expectedDaemonParam, extraParameters) + " -PonlyPreviousFailedTestClasses=true -PgithubToken=%github.ci.oauth.token%", getGradleStep("GRADLE_RERUNNER").gradleParams) assertEquals("clean myTask", getGradleStep("GRADLE_RUNNER").tasks) - assertEquals("myTask", getGradleStep("GRADLE_RERUNNER").tasks) + assertEquals("myTask tagBuild", getGradleStep("GRADLE_RERUNNER").tasks) } private @@ -127,5 +123,5 @@ class ApplyDefaultConfigurationTest { private fun expectedRunnerParam(daemon: String = "--daemon", extraParameters: String = "") = - "-PmaxParallelForks=%maxParallelForks% -s $daemon --continue -I \"%teamcity.build.checkoutDir%/gradle/init-scripts/build-scan.init.gradle.kts\" -Dorg.gradle.internal.tasks.createops -Dorg.gradle.internal.plugins.portal.url.override=http://dev12.gradle.org:8081/artifactory/gradle-plugins/ $extraParameters -PteamCityUsername=%teamcity.username.restbot% -PteamCityPassword=%teamcity.password.restbot% -PteamCityBuildId=%teamcity.build.id% \"-Dscan.tag.Check\" \"-Dscan.tag.\"" + "-PmaxParallelForks=%maxParallelForks% -s $daemon --continue -I \"%teamcity.build.checkoutDir%/gradle/init-scripts/build-scan.init.gradle.kts\" -Dorg.gradle.internal.tasks.createops -Dorg.gradle.internal.plugins.portal.url.override=%gradle.plugins.portal.url% $extraParameters -PteamCityUsername=%teamcity.username.restbot% -PteamCityPassword=%teamcity.password.restbot% -PteamCityBuildId=%teamcity.build.id% \"-Dscan.tag.Check\" \"-Dscan.tag.\"" } diff --git a/.teamcityTest/Gradle_Check_Tests/CIConfigIntegrationTests.kt b/.teamcityTest/Gradle_Check_Tests/CIConfigIntegrationTests.kt index f16db92fddecc..66aaa8a235170 100644 --- a/.teamcityTest/Gradle_Check_Tests/CIConfigIntegrationTests.kt +++ b/.teamcityTest/Gradle_Check_Tests/CIConfigIntegrationTests.kt @@ -1,12 +1,12 @@ +import common.JvmVendor +import common.JvmVersion +import common.NoBuildCache +import common.Os import configurations.shouldBeSkipped import jetbrains.buildServer.configs.kotlin.v2018_2.Project import model.CIBuildModel import model.GradleSubproject -import model.JvmVendor -import model.JvmVersion -import model.NoBuildCache -import model.OS import model.SpecificBuild import model.Stage import model.StageName @@ -14,8 +14,8 @@ import model.StageNames import model.TestCoverage import model.TestType import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Assertions.assertFalse +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test import projects.RootProject import java.io.File @@ -37,7 +37,7 @@ class CIConfigIntegrationTests { val macOS = readyForRelease.subProjects.find { it.name.contains("Macos") }!! macOS.buildTypes.forEach { buildType -> - assertFalse(OS.macos.ignoredSubprojects.any { subproject -> + assertFalse(Os.macos.ignoredSubprojects.any { subproject -> buildType.name.endsWith("($subproject)") }) } @@ -108,8 +108,8 @@ class CIConfigIntegrationTests { SpecificBuild.SanityCheck, SpecificBuild.BuildDistributions), functionalTests = listOf( - TestCoverage(TestType.quick, OS.linux, JvmVersion.java8), - TestCoverage(TestType.quick, OS.windows, JvmVersion.java11, vendor = JvmVendor.openjdk)), + TestCoverage(1, TestType.quick, Os.linux, JvmVersion.java8), + TestCoverage(2, TestType.quick, Os.windows, JvmVersion.java11, vendor = JvmVendor.openjdk)), omitsSlowProjects = true) ) ) @@ -131,23 +131,23 @@ class CIConfigIntegrationTests { stages = listOf( Stage(DefaultStageName("Stage1", "Stage1 description"), functionalTests = listOf( - TestCoverage(TestType.quick, OS.linux, JvmVersion.java8), - TestCoverage(TestType.quick, OS.windows, JvmVersion.java8)), + TestCoverage(1, TestType.quick, Os.linux, JvmVersion.java8), + TestCoverage(2, TestType.quick, Os.windows, JvmVersion.java8)), omitsSlowProjects = true), Stage(DefaultStageName("Stage2", "Stage2 description"), functionalTests = listOf( - TestCoverage(TestType.noDaemon, OS.linux, JvmVersion.java8), - TestCoverage(TestType.noDaemon, OS.windows, JvmVersion.java8)), + TestCoverage(3, TestType.noDaemon, Os.linux, JvmVersion.java8), + TestCoverage(4, TestType.noDaemon, Os.windows, JvmVersion.java8)), omitsSlowProjects = true), Stage(DefaultStageName("Stage3", "Stage3 description"), functionalTests = listOf( - TestCoverage(TestType.platform, OS.linux, JvmVersion.java8), - TestCoverage(TestType.platform, OS.windows, JvmVersion.java8)), + TestCoverage(5, TestType.platform, Os.linux, JvmVersion.java8), + TestCoverage(6, TestType.platform, Os.windows, JvmVersion.java8)), omitsSlowProjects = false), Stage(DefaultStageName("Stage4", "Stage4 description"), functionalTests = listOf( - TestCoverage(TestType.parallel, OS.linux, JvmVersion.java8), - TestCoverage(TestType.parallel, OS.windows, JvmVersion.java8)), + TestCoverage(7, TestType.parallel, Os.linux, JvmVersion.java8), + TestCoverage(8, TestType.parallel, Os.windows, JvmVersion.java8)), omitsSlowProjects = false) ), subProjects = listOf( @@ -240,16 +240,16 @@ class CIConfigIntegrationTests { @Test fun long_ids_are_shortened() { - val testCoverage = TestCoverage(TestType.quickFeedbackCrossVersion, OS.windows, JvmVersion.java11, JvmVendor.oracle) + val testCoverage = TestCoverage(1, TestType.quickFeedbackCrossVersion, Os.windows, JvmVersion.java11, JvmVendor.oracle) val shortenedId = testCoverage.asConfigurationId(CIBuildModel(), "veryLongSubprojectNameLongerThanEverythingWeHave") assertTrue(shortenedId.length < 80) - assertEquals(shortenedId, "Gradle_Check_QckFdbckCrssVrsn_Jv11_Orcl_Wndws_vryLngSbprjctNmLngrThnEvrythngWHv") + assertEquals("Gradle_Check_QckFdbckCrssVrsn_1_vryLngSbprjctNmLngrThnEvrythngWHv", shortenedId) - assertEquals("Gradle_Check_QuickFeedbackCrossVersion_Java11_Oracle_Windows_iIntegT", testCoverage.asConfigurationId(CIBuildModel(), "internalIntegTesting")) + assertEquals("Gradle_Check_QuickFeedbackCrossVersion_1_iIntegT", testCoverage.asConfigurationId(CIBuildModel(), "internalIntegTesting")) - assertEquals("Gradle_Check_QuickFeedbackCrossVersion_Java11_Oracle_Windows_buildCache", testCoverage.asConfigurationId(CIBuildModel(), "buildCache")) + assertEquals("Gradle_Check_QuickFeedbackCrossVersion_1_buildCache", testCoverage.asConfigurationId(CIBuildModel(), "buildCache")) - assertEquals("Gradle_Check_QuickFeedbackCrossVersion_Java11_Oracle_Windows_0", testCoverage.asConfigurationId(CIBuildModel())) + assertEquals("Gradle_Check_QuickFeedbackCrossVersion_1_0", testCoverage.asConfigurationId(CIBuildModel())) } private fun containsSrcFileWithString(srcRoot: File, content: String, exceptions: List): Boolean { diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8ead4df62f0b5..e22eb13c049c8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -100,6 +100,8 @@ Install: `./gradlew install -Pgradle_installPath=/any/path`. Use: `/any/path/bin You can debug Gradle by adding `-Dorg.gradle.debug=true` when executing. Gradle will wait for you to attach a debugger at `localhost:5005` by default. +If you made changes to build logic in `buildSrc`, you can test them by executing `./gradlew help -PbuildSrcCheck=true`. + ### Creating Commits And Writing Commit Messages The commit messages that accompany your code changes are an important piece of documentation, please follow these guidelines when writing commit messages: diff --git a/build.gradle.kts b/build.gradle.kts index 2f6638355830b..b8b86a1570559 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,7 +32,7 @@ plugins { // We have to apply it here at the moment, so that when the build scan plugin is auto-applied via --scan can detect that // the plugin has been already applied. For that the plugin has to be applied with the new plugin DSL syntax. com.gradle.`build-scan` - id("org.gradle.ci.tag-single-build") version("0.50") + id("org.gradle.ci.tag-single-build") version("0.63") } defaultTasks("assemble") diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index dcbf7834bcb39..ea7c3c8bcc1bd 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -14,18 +14,15 @@ * limitations under the License. */ -import org.gradle.plugins.ide.idea.model.IdeaModel - import org.gradle.kotlin.dsl.plugins.dsl.KotlinDslPlugin -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - import java.io.File import java.util.Properties plugins { - `kotlin-dsl` - id("org.gradle.kotlin-dsl.ktlint-convention") version "0.2.3" apply false + `java` + `kotlin-dsl` apply false + id("org.gradle.kotlin-dsl.ktlint-convention") version "0.3.0" apply false } subprojects { @@ -41,7 +38,7 @@ subprojects { applyKotlinProjectConventions() } - configure { + java { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } @@ -98,8 +95,25 @@ dependencies { } } -// Set gradlebuild.skipBuildSrcChecks Gradle property to "true" to disable all buildSrc verification tasks -if (findProperty("gradlebuild.skipBuildSrcChecks") == "true") { + +// TODO Avoid duplication of what defines a CI Server with BuildEnvironment +val isCiServer: Boolean by extra { "CI" in System.getenv() } + + +/** + * Controls whether verification tasks are skipped. + * + * Set the `buildSrcCheck` Gradle property to `true` to run the verification tasks. + * Set it to `false` to skip the verification tasks. + * + * When that property is unset, defaults to `false` on CI, to `true` otherwise. + */ +val isSkipBuildSrcVerification: Boolean = + (findProperty("buildSrcCheck") as String?) + ?.let { it == "false" } + ?: !isCiServer + +if (isSkipBuildSrcVerification) { allprojects { tasks.matching { it.group == LifecycleBasePlugin.VERIFICATION_GROUP }.configureEach { enabled = false @@ -107,8 +121,6 @@ if (findProperty("gradlebuild.skipBuildSrcChecks") == "true") { } } -// TODO Avoid duplication of what defines a CI Server with BuildEnvironment -val isCiServer: Boolean by extra { "CI" in System.getenv() } if (isCiServer) { gradle.buildFinished { allprojects.forEach { project -> @@ -185,7 +197,7 @@ fun Project.applyGroovyProjectConventions() { } tasks.withType().configureEach { - if (JavaVersion.current().isJava9Compatible()) { + if (JavaVersion.current().isJava9Compatible) { //allow ProjectBuilder to inject legacy types into the system classloader jvmArgs("--add-opens", "java.base/java.lang=ALL-UNNAMED") jvmArgs("--illegal-access=deny") @@ -211,8 +223,15 @@ fun Project.applyKotlinProjectConventions() { apply(plugin = "org.gradle.kotlin-dsl.ktlint-convention") plugins.withType { - kotlinDslPluginOptions { + configure { experimentalWarning.set(false) } } + + configure { + // TODO:kotlin-dsl remove precompiled script plugins accessors exclusion from ktlint checks + filter { + exclude("gradle/kotlin/dsl/accessors/_*/**") + } + } } diff --git a/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/ExplodeZipAndFindJars.groovy b/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/ExplodeZipAndFindJars.groovy index 36434bad68c10..40cbd998e1492 100644 --- a/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/ExplodeZipAndFindJars.groovy +++ b/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/ExplodeZipAndFindJars.groovy @@ -18,40 +18,41 @@ package org.gradle.binarycompatibility.transforms import groovy.transform.CompileStatic -import org.gradle.api.artifacts.transform.ArtifactTransform +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import java.nio.file.Files import java.util.zip.ZipEntry import java.util.zip.ZipInputStream -import java.nio.file.Files @CompileStatic -class ExplodeZipAndFindJars extends ArtifactTransform { +abstract class ExplodeZipAndFindJars implements TransformAction { + + @PathSensitive(PathSensitivity.NAME_ONLY) + @InputArtifact + abstract File getArtifact() @Override - List transform(final File file) { - List result = [] - if (outputDirectory.exists() && outputDirectory.listFiles().length == 0) { - File gradleJars = new File(outputDirectory, "gradle-jars") - File dependencies = new File(outputDirectory, "gradle-dependencies") - gradleJars.mkdir() - dependencies.mkdir() - result << gradleJars - result << dependencies - ZipInputStream zin = new ZipInputStream(file.newInputStream()) - ZipEntry zipEntry - while (zipEntry = zin.nextEntry) { - String shortName = zipEntry.name - if (shortName.contains('/')) { - shortName = shortName.substring(shortName.lastIndexOf('/') + 1) - } - if (shortName.endsWith('.jar')) { - def outputDir = shortName.startsWith('gradle-') ? gradleJars : dependencies - def out = new File(outputDir, shortName) - Files.copy(zin, out.toPath()) - zin.closeEntry() - } + void transform(TransformOutputs outputs) { + File gradleJars = outputs.dir("gradle-jars") + File dependencies = outputs.dir("gradle-dependencies") + ZipInputStream zin = new ZipInputStream(artifact.newInputStream()) + ZipEntry zipEntry + while (zipEntry = zin.nextEntry) { + String shortName = zipEntry.name + if (shortName.contains('/')) { + shortName = shortName.substring(shortName.lastIndexOf('/') + 1) + } + if (shortName.endsWith('.jar')) { + def outputDir = shortName.startsWith('gradle-') ? gradleJars : dependencies + def out = new File(outputDir, shortName) + Files.copy(zin, out.toPath()) + zin.closeEntry() } } - result } } diff --git a/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/FindGradleClasspath.groovy b/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/FindGradleClasspath.groovy index 7b7456e1b8be6..531fec291900c 100644 --- a/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/FindGradleClasspath.groovy +++ b/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/FindGradleClasspath.groovy @@ -14,26 +14,29 @@ * limitations under the License. */ - package org.gradle.binarycompatibility.transforms -import org.gradle.api.artifacts.transform.ArtifactTransform -import javax.inject.Inject import groovy.transform.CompileStatic +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity @CompileStatic -class FindGradleClasspath extends ArtifactTransform { +abstract class FindGradleClasspath implements TransformAction { - @Inject - FindGradleClasspath() { - } + @PathSensitive(PathSensitivity.NAME_ONLY) + @InputArtifact + abstract File getArtifact() @Override - List transform(final File file) { - if (file.name == 'gradle-dependencies') { - (file.listFiles() as List).sort { it.name } - } else { - [] + void transform(TransformOutputs outputs) { + if (artifact.name == 'gradle-dependencies') { + (artifact.listFiles() as List).sort { it.name }.each { + outputs.file(it) + } } } } diff --git a/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/FindGradleJar.groovy b/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/FindGradleJar.groovy index 80a362563ad56..bf9d61d50f483 100644 --- a/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/FindGradleJar.groovy +++ b/buildSrc/subprojects/binary-compatibility/src/main/groovy/org/gradle/binarycompatibility/transforms/FindGradleJar.groovy @@ -17,25 +17,37 @@ package org.gradle.binarycompatibility.transforms -import org.gradle.api.artifacts.transform.ArtifactTransform -import javax.inject.Inject import groovy.transform.CompileStatic +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity @CompileStatic -class FindGradleJar extends ArtifactTransform { - private final String target +abstract class FindGradleJar implements TransformAction { - @Inject - FindGradleJar(String target) { - this.target = target + @CompileStatic + interface Parameters extends TransformParameters { + @Input + String getTarget() + void setTarget(String target) } + @PathSensitive(PathSensitivity.NAME_ONLY) + @InputArtifact + abstract File getArtifact() + @Override - List transform(final File file) { - if (file.name == 'gradle-jars') { - (file.listFiles().findAll { it.name.startsWith("gradle-${target}-") } as List).sort { it.name } - } else { - [] + void transform(TransformOutputs outputs) { + if (artifact.name == 'gradle-jars') { + (artifact.listFiles().findAll { + it.name.startsWith("gradle-${parameters.target}-") + } as List).sort { it.name }.each { + outputs.file(it) + } } } } diff --git a/buildSrc/subprojects/build/build.gradle.kts b/buildSrc/subprojects/build/build.gradle.kts index e60abe0eb4c81..a5b22dd1095f1 100644 --- a/buildSrc/subprojects/build/build.gradle.kts +++ b/buildSrc/subprojects/build/build.gradle.kts @@ -1,6 +1,6 @@ dependencies { api("com.google.guava:guava:26.0-jre") - api("org.asciidoctor:asciidoctor-gradle-plugin:1.5.9.2") + api("org.asciidoctor:asciidoctor-gradle-plugin:1.5.10") implementation(project(":buildPlatform")) implementation("org.asciidoctor:asciidoctorj:1.5.8.1") diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/GradleStartScriptGenerator.groovy b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/GradleStartScriptGenerator.groovy index 2fb3c0878dc55..21d6d31b37534 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/GradleStartScriptGenerator.groovy +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/GradleStartScriptGenerator.groovy @@ -62,7 +62,7 @@ class GradleStartScriptGenerator extends DefaultTask { generator.scriptRelPath = 'bin/gradle' generator.classpath = ["lib/${launcherJar.singleFile.name}" as String] generator.appNameSystemProperty = 'org.gradle.appname' - generator.defaultJvmOpts = ["-Xmx64m"] + generator.defaultJvmOpts = ["-Xmx64m", "-Xms64m"] generator.generateUnixScript(shellScript) generator.generateWindowsScript(batchFile) } diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/ElementWarningsRenderer.java b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/ElementWarningsRenderer.java index f06f9914d5050..bf1ef2654e1d6 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/ElementWarningsRenderer.java +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/ElementWarningsRenderer.java @@ -48,5 +48,13 @@ public void renderTo(DslElementDoc elementDoc, String type, Element parent) { link.appendChild(document.createTextNode("incubating")); para.appendChild(document.createTextNode(" and may change in a future version of Gradle.")); } + if (elementDoc.isReplaced()) { + Document document = parent.getOwnerDocument(); + Element caution = document.createElement("caution"); + parent.appendChild(caution); + Element para = document.createElement("para"); + caution.appendChild(para); + para.appendChild(document.createTextNode(String.format("Note: This %s has been replaced by %s.", type, elementDoc.getReplacement()))); + } } } diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDetailRenderer.java b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDetailRenderer.java index 5f2f52ac5f96d..3cda1b7f2e8c0 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDetailRenderer.java +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyDetailRenderer.java @@ -46,10 +46,13 @@ public void renderTo(PropertyDoc propertyDoc, Element parent) { Element literal = document.createElement("literal"); title.appendChild(literal); literal.appendChild(document.createTextNode(propertyDoc.getName())); - if (!propertyDoc.getMetaData().isWriteable()) { - title.appendChild(document.createTextNode(" (read-only)")); - } else if (!propertyDoc.getMetaData().isReadable()) { - title.appendChild(document.createTextNode(" (write-only)")); + + if (!propertyDoc.getMetaData().isProviderApi()) { + if (!propertyDoc.getMetaData().isWriteable()) { + title.appendChild(document.createTextNode(" (read-only)")); + } else if (!propertyDoc.getMetaData().isReadable()) { + title.appendChild(document.createTextNode(" (write-only)")); + } } warningsRenderer.renderTo(propertyDoc, "property", section); diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyTableRenderer.java b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyTableRenderer.java index acb56c1afdf49..3f878b01b133e 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyTableRenderer.java +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/PropertyTableRenderer.java @@ -70,6 +70,11 @@ public void renderTo(Iterable properties, Element parent) { td.appendChild(caution); caution.appendChild(document.createTextNode("Incubating")); } + if (propDoc.isReplaced()) { + Element caution = document.createElement("caution"); + td.appendChild(caution); + caution.appendChild(document.createTextNode("Replaced")); + } td.appendChild(document.importNode(propDoc.getDescription(), true)); } } diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/BlockDoc.groovy b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/BlockDoc.groovy index 4e4d553c67bfe..5c4f4b2c78e43 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/BlockDoc.groovy +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/BlockDoc.groovy @@ -67,6 +67,15 @@ class BlockDoc implements DslElementDoc { return blockProperty.incubating || blockMethod.incubating } + boolean isReplaced() { + return blockProperty.replaced + } + + @Override + String getReplacement() { + return blockProperty.replacement + } + PropertyDoc getBlockProperty() { return blockProperty } diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassDoc.groovy b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassDoc.groovy index dfefd2317e2fa..ad5acffd6b4c1 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassDoc.groovy +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/ClassDoc.groovy @@ -73,6 +73,15 @@ class ClassDoc implements DslElementDoc { return classMetaData.incubating } + boolean isReplaced() { + return classMetaData.replaced + } + + @Override + String getReplacement() { + return classMetaData.replacement + } + Collection getClassProperties() { return classProperties } void addClassProperty(PropertyDoc propertyDoc) { diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/DslElementDoc.java b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/DslElementDoc.java index 98b0742456db2..7dd325cd13d4f 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/DslElementDoc.java +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/DslElementDoc.java @@ -30,4 +30,8 @@ public interface DslElementDoc { boolean isDeprecated(); boolean isIncubating(); + + boolean isReplaced(); + + String getReplacement(); } diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MethodDoc.groovy b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MethodDoc.groovy index 6f685c4f66a5b..c463574d75513 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MethodDoc.groovy +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/MethodDoc.groovy @@ -64,6 +64,15 @@ class MethodDoc implements DslElementDoc { return metaData.incubating || metaData.ownerClass.incubating } + boolean isReplaced() { + return metaData.replaced + } + + @Override + String getReplacement() { + return metaData.replacement + } + Element getDescription() { return comment.find { it.nodeName == 'para' } } diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/PropertyDoc.groovy b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/PropertyDoc.groovy index 47e6a0ac25873..9ac89ef680f18 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/PropertyDoc.groovy +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/docbook/model/PropertyDoc.groovy @@ -75,6 +75,15 @@ class PropertyDoc implements DslElementDoc { return metaData.incubating || metaData.ownerClass.incubating } + boolean isReplaced() { + return metaData.replaced + } + + @Override + String getReplacement() { + return metaData.replacement + } + Element getDescription() { return comment.find { it.nodeName == 'para' } } diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java index 4e5153ef819e3..e949ada6c20ba 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/SourceMetaDataVisitor.java @@ -28,6 +28,7 @@ import com.github.javaparser.ast.expr.AnnotationExpr; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.LiteralStringValueExpr; +import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations; import com.github.javaparser.ast.nodeTypes.NodeWithExtends; import com.github.javaparser.ast.nodeTypes.NodeWithImplements; @@ -35,11 +36,18 @@ import com.github.javaparser.ast.type.ClassOrInterfaceType; import com.github.javaparser.ast.type.Type; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; -import org.gradle.build.docs.dsl.source.model.*; +import org.gradle.build.docs.dsl.source.model.AbstractLanguageElement; +import org.gradle.build.docs.dsl.source.model.ClassMetaData; import org.gradle.build.docs.dsl.source.model.ClassMetaData.MetaType; +import org.gradle.build.docs.dsl.source.model.MethodMetaData; +import org.gradle.build.docs.dsl.source.model.PropertyMetaData; +import org.gradle.build.docs.dsl.source.model.TypeMetaData; import org.gradle.build.docs.model.ClassMetaDataRepository; -import java.util.*; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -149,6 +157,7 @@ public void visit(MethodDeclaration methodDeclaration, ClassMetaDataRepository node, AbstractLanguageElement currentElement) { for (AnnotationExpr child : node.getAnnotations()) { + if (child instanceof SingleMemberAnnotationExpr && child.getNameAsString().endsWith("ReplacedBy")) { + currentElement.setReplacement(((SingleMemberAnnotationExpr) child).getMemberValue().asLiteralStringValueExpr().getValue()); + } currentElement.addAnnotationTypeName(child.getNameAsString()); } } diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/model/AbstractLanguageElement.java b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/model/AbstractLanguageElement.java index dcc0677ba09e7..e2487d54b98da 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/model/AbstractLanguageElement.java +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/model/AbstractLanguageElement.java @@ -26,6 +26,7 @@ public abstract class AbstractLanguageElement implements LanguageElement, Serializable { private String rawCommentText; private final List annotationNames = new ArrayList(); + private String replacement; protected AbstractLanguageElement() { } @@ -58,6 +59,18 @@ public boolean isIncubating() { return annotationNames.contains("org.gradle.api.Incubating"); } + public boolean isReplaced() { + return annotationNames.contains("org.gradle.api.model.ReplacedBy"); + } + + public String getReplacement() { + return replacement; + } + + public void setReplacement(String replacement) { + this.replacement = replacement; + } + public void resolveTypes(Transformer transformer) { for (int i = 0; i < annotationNames.size(); i++) { annotationNames.set(i, transformer.transform(annotationNames.get(i))); diff --git a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaData.java b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaData.java index f2822416dfcbc..a9c195ab03379 100644 --- a/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaData.java +++ b/buildSrc/subprojects/build/src/main/groovy/org/gradle/build/docs/dsl/source/model/PropertyMetaData.java @@ -59,6 +59,11 @@ public boolean isReadable() { return getter != null; } + public boolean isProviderApi() { + // TODO: Crude approximation + return setter == null && (getType().getName().contains("Provider") || getType().getName().contains("Property")); + } + public ClassMetaData getOwnerClass() { return ownerClass; } diff --git a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/TaskPropertyValidationPlugin.kt b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/TaskPropertyValidationPlugin.kt index 11ffcbe9a9d71..6b1157ade0615 100644 --- a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/TaskPropertyValidationPlugin.kt +++ b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/TaskPropertyValidationPlugin.kt @@ -17,10 +17,13 @@ package org.gradle.gradlebuild.buildquality import accessors.java import accessors.reporting + import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.Usage import org.gradle.api.tasks.testing.Test + import org.gradle.kotlin.dsl.* import org.gradle.plugin.devel.tasks.ValidateTaskProperties @@ -36,28 +39,29 @@ const val reportFileName = "task-properties/report.txt" open class TaskPropertyValidationPlugin : Plugin { override fun apply(project: Project): Unit = project.run { - project.afterEvaluate { + afterEvaluate { // This is in an after evaluate block to defer the decision until after the `java-gradle-plugin` may have been applied, so as to not collide with it // It would be better to use some convention plugins instead, that apply a fixes set of plugins (including this one) - if (plugins.hasPlugin("java-base") && !plugins.hasPlugin("java-gradle-plugin")) { - configurations.create("validationRuntime") { + if (plugins.hasPlugin("java-base")) { + val validationRuntime by configurations.creating { isCanBeConsumed = false - attributes.attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage.JAVA_RUNTIME)) + attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) } - dependencies.add("validationRuntime", dependencies.project(":distributions")) - val validateTask = tasks.register(validateTaskName, ValidateTaskProperties::class) { - val main by java.sourceSets - dependsOn(main.output) - classes.setFrom(main.output.classesDirs) - classpath.from(main.output) // to pick up resources too - classpath.from(main.runtimeClasspath) - classpath.from(configurations["validationRuntime"]) - // TODO Should we provide a more intuitive way in the task definition to configure this property from Kotlin? - outputFile.set(reporting.baseDirectory.file(reportFileName)) - failOnWarning = true - enableStricterValidation = true + dependencies.add( + validationRuntime.name, + dependencies.project(":distributions") + ) + + val validateTask = if (plugins.hasPlugin("java-gradle-plugin")) { + tasks.named(validateTaskName, ValidateTaskProperties::class) + } else { + tasks.register(validateTaskName, ValidateTaskProperties::class) } - tasks.named("codeQuality").configure { + + validateTask { + configureValidateTask(validationRuntime) + } + tasks.named("codeQuality") { dependsOn(validateTask) } tasks.withType(Test::class).configureEach { @@ -66,4 +70,18 @@ open class TaskPropertyValidationPlugin : Plugin { } } } + + private + fun ValidateTaskProperties.configureValidateTask(validationRuntime: Configuration) { + val main by project.java.sourceSets + dependsOn(main.output) + classes.setFrom(main.output.classesDirs) + classpath.from(main.output) // to pick up resources too + classpath.from(main.runtimeClasspath) + classpath.from(validationRuntime) + // TODO Should we provide a more intuitive way in the task definition to configure this property from Kotlin? + outputFile.set(project.reporting.baseDirectory.file(reportFileName)) + failOnWarning = true + enableStricterValidation = true + } } diff --git a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/classycle/ClassyclePlugin.kt b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/classycle/ClassyclePlugin.kt index 7b5197f796275..d142ec17fdae2 100644 --- a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/classycle/ClassyclePlugin.kt +++ b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/classycle/ClassyclePlugin.kt @@ -39,7 +39,7 @@ open class ClassyclePlugin : Plugin { val classycle = tasks.register("classycle") java.sourceSets.all { val taskName = getTaskName("classycle", null) - val sourceSetTask = project.tasks.register( + val sourceSetTask = tasks.register( taskName, Classycle::class.java, output.classesDirs, @@ -48,9 +48,9 @@ open class ClassyclePlugin : Plugin { reporting.file("classycle"), extension.reportResourcesZip ) - classycle.configure { dependsOn(sourceSetTask) } - tasks.named("check").configure { dependsOn(sourceSetTask) } - tasks.named("codeQuality").configure { dependsOn(sourceSetTask) } + classycle { dependsOn(sourceSetTask) } + tasks.named("check") { dependsOn(sourceSetTask) } + tasks.named("codeQuality") { dependsOn(sourceSetTask) } } } } diff --git a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubatingApiAggregateReportTask.kt b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubatingApiAggregateReportTask.kt index 57271ff3f01c0..9d0061ba8c0d6 100644 --- a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubatingApiAggregateReportTask.kt +++ b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubatingApiAggregateReportTask.kt @@ -50,7 +50,7 @@ open class IncubatingApiAggregateReportTask @TaskAction fun generateReport() { workerExecutor.submit(GenerateReport::class.java) { - isolationMode = IsolationMode.CLASSLOADER + isolationMode = IsolationMode.NONE params(reports, htmlReportFile.asFile.get()) } } diff --git a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubatingApiReportTask.kt b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubatingApiReportTask.kt index 00c5eafd9d5b9..c72d13dc2047e 100644 --- a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubatingApiReportTask.kt +++ b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubatingApiReportTask.kt @@ -15,6 +15,7 @@ import com.github.javaparser.symbolsolver.JavaSymbolSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver + import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty @@ -29,13 +30,27 @@ import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.gradle.workers.IsolationMode import org.gradle.workers.WorkerExecutor + +import org.jetbrains.kotlin.psi.KtCallableDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtNamedDeclaration +import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType + +import parser.KotlinSourceParser + import java.io.File import javax.inject.Inject +import org.gradle.kotlin.dsl.* + @CacheableTask -open class IncubatingApiReportTask - @Inject constructor(private val workerExecutor: WorkerExecutor) : DefaultTask() { +open class IncubatingApiReportTask @Inject constructor( + + private + val workerExecutor: WorkerExecutor + +) : DefaultTask() { @InputFile @PathSensitive(PathSensitivity.RELATIVE) @@ -46,35 +61,53 @@ open class IncubatingApiReportTask val releasedVersionsFile: RegularFileProperty = project.objects.fileProperty() @Input - val title: Property = project.objects.property(String::class.java).also { - it.set(project.provider { project.name }) - } + val title: Property = project.objects.property().convention(project.provider { + project.name + }) @InputFiles @PathSensitive(PathSensitivity.RELATIVE) - val sources: ConfigurableFileCollection = project.files() + val sources: ConfigurableFileCollection = project.objects.fileCollection() @OutputFile - val htmlReportFile: RegularFileProperty = project.objects.fileProperty().also { - it.set(project.layout.buildDirectory.file("reports/incubating.html")) - } + val htmlReportFile: RegularFileProperty = project.objects.fileProperty().convention( + project.layout.buildDirectory.file("reports/incubating.html") + ) @OutputFile - val textReportFile: RegularFileProperty = project.objects.fileProperty().also { - it.set(project.layout.buildDirectory.file("reports/incubating.txt")) - } + val textReportFile: RegularFileProperty = project.objects.fileProperty().convention( + project.layout.buildDirectory.file("reports/incubating.txt") + ) @TaskAction + @Suppress("unused") fun analyze() = workerExecutor.submit(Analyzer::class.java) { isolationMode = IsolationMode.NONE - params(versionFile.asFile.get(), sources.files, htmlReportFile.asFile.get(), textReportFile.asFile.get(), title.get(), releasedVersionsFile.asFile.get()) + params( + sources.files, + htmlReportFile.asFile.get(), + textReportFile.asFile.get(), + title.get(), + releasedVersionsFile.asFile.get() + ) } } +private +typealias Version = String + + +private +typealias IncubatingDescription = String + + +private +typealias VersionsToIncubating = Map> + + open class Analyzer @Inject constructor( - private val versionFile: File, private val srcDirs: Set, private val htmlReportFile: File, private val textReportFile: File, @@ -84,17 +117,20 @@ class Analyzer @Inject constructor( override fun run() { - val versionToIncubating = mutableMapOf>() + val versionToIncubating = mutableMapOf>() srcDirs.forEach { srcDir -> if (srcDir.exists()) { - val solver = JavaSymbolSolver(CombinedTypeSolver(JavaParserTypeSolver(srcDir), ReflectionTypeSolver())) - srcDir.walkTopDown().forEach { - if (it.name.endsWith(".java")) { - try { - parseJavaFile(it, versionToIncubating, solver) - } catch (e: Exception) { - println("Unable to parse $it: ${e.message}") + val collector = CompositeVersionsToIncubatingCollector(listOf( + JavaVersionsToIncubatingCollector(srcDir), + KotlinVersionsToIncubatingCollector(srcDir) + )) + srcDir.walkTopDown().forEach { sourceFile -> + try { + collector.collectFrom(sourceFile).forEach { (version, incubating) -> + versionToIncubating.getOrPut(version) { mutableSetOf() }.addAll(incubating) } + } catch (e: Exception) { + throw Exception("Unable to parse $sourceFile: ${e.message}") } } } @@ -104,61 +140,7 @@ class Analyzer @Inject constructor( } private - fun parseJavaFile(file: File, versionToIncubating: MutableMap>, solver: JavaSymbolSolver) = - JavaParser.parse(file).run { - solver.inject(this) - findAll(Node::class.java) - .filter { - it is NodeWithAnnotations<*> && - it.annotations.any { it.nameAsString == "Incubating" } - }.map { - val node = it as NodeWithAnnotations<*> - val version = findVersionFromJavadoc(node) - ?: "Not found" - Pair(version, nodeName(it, this, file)) - }.forEach { - versionToIncubating.getOrPut(it.first) { - mutableSetOf() - }.add(it.second) - } - } - - - private - fun findVersionFromJavadoc(node: NodeWithAnnotations<*>): String? = if (node is NodeWithJavadoc<*>) { - node.javadoc.map { - it.blockTags.find { - it.tagName == "since" - }?.content?.elements?.find { - it is JavadocSnippet - }?.toText() - }.orElse(null) - } else { - null - } - - private - fun nodeName(it: Node?, unit: CompilationUnit, file: File) = when (it) { - is EnumDeclaration -> tryResolve({ it.resolve().qualifiedName }) { inferClassName(unit) } - is ClassOrInterfaceDeclaration -> tryResolve({ it.resolve().qualifiedName }) { inferClassName(unit) } - is MethodDeclaration -> tryResolve({ it.resolve().qualifiedSignature }) { inferClassName(unit) } - is AnnotationDeclaration -> tryResolve({ it.resolve().qualifiedName }) { inferClassName(unit) } - is NodeWithSimpleName<*> -> it.nameAsString - else -> unit.primaryTypeName.orElse(file.name) - } - - private - fun inferClassName(unit: CompilationUnit) = "${unit.packageDeclaration.map { it.nameAsString }.orElse("")}.${unit.primaryTypeName.orElse("")}" - - private - inline fun tryResolve(resolver: () -> String, or: () -> String) = try { - resolver() - } catch (e: Throwable) { - or() - } - - private - fun generateHtmlReport(versionToIncubating: Map>) { + fun generateHtmlReport(versionToIncubating: VersionsToIncubating) { htmlReportFile.parentFile.mkdirs() htmlReportFile.printWriter(Charsets.UTF_8).use { writer -> writer.println(""" @@ -174,13 +156,13 @@ class Analyzer @Inject constructor(

Incubating APIs for $title

""") val versions = versionsDates() - versionToIncubating.toSortedMap().forEach { - writer.println("") - writer.println("

Incubating since ${it.key} (${versions.get(it.key)?.run { "released on $this" } + versionToIncubating.toSortedMap().forEach { (version, incubatingDescriptions) -> + writer.println("") + writer.println("

Incubating since $version (${versions[version]?.run { "released on $this" } ?: "unreleased"})

") writer.println("
    ") - it.value.sorted().forEach { - writer.println("
  • ${it.escape()}
  • ") + incubatingDescriptions.sorted().forEach { incubating -> + writer.println("
  • ${incubating.escape()}
  • ") } writer.println("
") } @@ -189,23 +171,22 @@ class Analyzer @Inject constructor( } private - fun generateTextReport(versionToIncubating: Map>) { + fun generateTextReport(versionToIncubating: VersionsToIncubating) { textReportFile.parentFile.mkdirs() textReportFile.printWriter(Charsets.UTF_8).use { writer -> val versions = versionsDates() - versionToIncubating.toSortedMap().forEach { - val version = it.key - val releaseDate = versions.get(it.key) ?: "unreleased" - it.value.sorted().forEach { - writer.println("$version;$releaseDate;$it") + versionToIncubating.toSortedMap().forEach { (version, incubatingDescriptions) -> + val releaseDate = versions[version] ?: "unreleased" + incubatingDescriptions.sorted().forEach { incubating -> + writer.println("$version;$releaseDate;$incubating") } } } } private - fun versionsDates(): Map { - val versions = mutableMapOf() + fun versionsDates(): Map { + val versions = mutableMapOf() var version: String? = null releasedVersionsFile.forEachLine(Charsets.UTF_8) { val line = it.trim() @@ -215,7 +196,7 @@ class Analyzer @Inject constructor( if (line.startsWith("\"buildTime\"")) { var date = line.substring(line.indexOf("\"", 12) + 1, line.lastIndexOf("\"")) date = date.substring(0, 4) + "-" + date.substring(4, 6) + "-" + date.substring(6, 8) - versions.put(version!!, date) + versions[version!!] = date } } return versions @@ -225,3 +206,163 @@ class Analyzer @Inject constructor( private fun String.escape() = replace("<", "<").replace(">", ">") } + + +private +interface VersionsToIncubatingCollector { + + fun collectFrom(sourceFile: File): VersionsToIncubating +} + + +private +class CompositeVersionsToIncubatingCollector( + + private + val collectors: List + +) : VersionsToIncubatingCollector { + + override fun collectFrom(sourceFile: File): VersionsToIncubating = + collectors + .flatMap { it.collectFrom(sourceFile).entries } + .associate { it.key to it.value } +} + + +private +const val versionNotFound = "Not found" + + +private +class JavaVersionsToIncubatingCollector(srcDir: File) : VersionsToIncubatingCollector { + + private + val solver = JavaSymbolSolver(CombinedTypeSolver(JavaParserTypeSolver(srcDir), ReflectionTypeSolver())) + + override fun collectFrom(sourceFile: File): VersionsToIncubating { + + if (!sourceFile.name.endsWith(".java")) return emptyMap() + + val versionsToIncubating = mutableMapOf>() + JavaParser.parse(sourceFile).run { + solver.inject(this) + findAllIncubating() + .map { node -> toVersionIncubating(sourceFile, node) } + .forEach { (version, incubating) -> + versionsToIncubating.getOrPut(version) { mutableSetOf() }.add(incubating) + } + } + return versionsToIncubating + } + + private + fun CompilationUnit.findAllIncubating() = + findAll(Node::class.java).filter { it.isIncubating } + + private + val Node.isIncubating: Boolean + get() = (this as? NodeWithAnnotations<*>)?.annotations?.any { it.nameAsString == "Incubating" } ?: false + + private + fun CompilationUnit.toVersionIncubating(sourceFile: File, node: Node) = + Pair( + findVersionFromJavadoc(node as NodeWithAnnotations<*>) ?: versionNotFound, + nodeName(node, this, sourceFile) + ) + + private + fun findVersionFromJavadoc(node: NodeWithAnnotations<*>): String? = + (node as? NodeWithJavadoc<*>)?.javadoc?.map { javadoc -> + javadoc.blockTags + .find { tag -> tag.tagName == "since" } + ?.content?.elements?.find { description -> description is JavadocSnippet } + ?.toText() + }?.orElse(null) + + private + fun nodeName(it: Node?, unit: CompilationUnit, file: File) = when (it) { + is EnumDeclaration -> tryResolve({ it.resolve().qualifiedName }) { inferClassName(unit) } + is ClassOrInterfaceDeclaration -> tryResolve({ it.resolve().qualifiedName }) { inferClassName(unit) } + is MethodDeclaration -> tryResolve({ it.resolve().qualifiedSignature }) { inferClassName(unit) } + is AnnotationDeclaration -> tryResolve({ it.resolve().qualifiedName }) { inferClassName(unit) } + is NodeWithSimpleName<*> -> it.nameAsString + else -> unit.primaryTypeName.orElse(file.name) + } + + private + fun inferClassName(unit: CompilationUnit) = + "${unit.packageDeclaration.map { it.nameAsString }.orElse("")}.${unit.primaryTypeName.orElse("")}" + + private + inline fun tryResolve(resolver: () -> String, or: () -> String) = try { + resolver() + } catch (e: Throwable) { + or() + } +} + + +private +class KotlinVersionsToIncubatingCollector(srcDir: File) : VersionsToIncubatingCollector { + + override fun collectFrom(sourceFile: File): VersionsToIncubating { + + if (!sourceFile.name.endsWith(".kt")) return emptyMap() + + val versionsToIncubating = mutableMapOf>() + KotlinSourceParser().mapParsedKotlinFiles(sourceFile) { ktFile -> + ktFile.forEachIncubatingDeclaration { declaration -> + versionsToIncubating + .getOrPut(declaration.sinceVersion) { mutableSetOf() } + .add(buildIncubatingDescription(sourceFile, ktFile, declaration)) + } + } + return versionsToIncubating + } + + private + fun buildIncubatingDescription(sourceFile: File, ktFile: KtFile, declaration: KtNamedDeclaration): String { + var incubating = "${declaration.typeParametersString}${declaration.fullyQualifiedName}" + if (declaration is KtCallableDeclaration) { + incubating += declaration.valueParametersString + declaration.receiverTypeString?.let { receiver -> + incubating += " with $receiver receiver" + } + if (declaration.parent == ktFile) { + incubating += ", top-level in ${sourceFile.name}" + } + } + return incubating + } + + private + fun KtFile.forEachIncubatingDeclaration(block: (KtNamedDeclaration) -> Unit) { + collectDescendantsOfType().filter { it.isIncubating }.forEach(block) + } + + private + val KtNamedDeclaration.isIncubating: Boolean + get() = annotationEntries.any { it.shortName?.asString() == "Incubating" } + + private + val KtNamedDeclaration.typeParametersString: String + get() = (this as? KtCallableDeclaration)?.typeParameterList?.let { "${it.text} " } ?: "" + + private + val KtNamedDeclaration.fullyQualifiedName: String + get() = fqName!!.asString() + + private + val KtCallableDeclaration.valueParametersString: String + get() = valueParameterList?.text ?: "" + + private + val KtCallableDeclaration.receiverTypeString: String? + get() = receiverTypeReference?.text + + private + val KtNamedDeclaration.sinceVersion: String + get() = docComment?.getDefaultSection()?.findTagsByName("since")?.singleOrNull()?.getContent() + ?: versionNotFound +} diff --git a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubationReportPlugin.kt b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubationReportPlugin.kt index af37536b85002..8546ce3ec63d9 100644 --- a/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubationReportPlugin.kt +++ b/buildSrc/subprojects/buildquality/src/main/kotlin/org/gradle/gradlebuild/buildquality/incubation/IncubationReportPlugin.kt @@ -17,23 +17,29 @@ package org.gradle.gradlebuild.buildquality.incubation import accessors.java +import build.kotlin import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.* class IncubationReportPlugin : Plugin { - override fun apply(project: Project) = project.run { - val reportTask = tasks.register("incubationReport", IncubatingApiReportTask::class) { - val main by java.sourceSets + override fun apply(project: Project): Unit = project.run { + val main by java.sourceSets + val reportTask = tasks.register("incubationReport") { description = "Generates a report of incubating APIS" title.set(project.name) versionFile.set(rootProject.file("version.txt")) releasedVersionsFile.set(rootProject.file("released-versions.json")) - sources.setFrom(main.java.sourceDirectories) + sources.from(main.java.sourceDirectories) htmlReportFile.set(file("$buildDir/reports/incubation/${project.name}.html")) textReportFile.set(file("$buildDir/reports/incubation/${project.name}.txt")) } - tasks.named("check").configure { dependsOn(reportTask) } + plugins.withId("org.jetbrains.kotlin.jvm") { + reportTask { + sources.from(main.kotlin.sourceDirectories) + } + } + tasks.named("check") { dependsOn(reportTask) } } } diff --git a/buildSrc/subprojects/cleanup/src/main/kotlin/org/gradle/testing/LeakingProcessKillPattern.kt b/buildSrc/subprojects/cleanup/src/main/kotlin/org/gradle/testing/LeakingProcessKillPattern.kt index bdc30e3086bee..bed66afcebba0 100644 --- a/buildSrc/subprojects/cleanup/src/main/kotlin/org/gradle/testing/LeakingProcessKillPattern.kt +++ b/buildSrc/subprojects/cleanup/src/main/kotlin/org/gradle/testing/LeakingProcessKillPattern.kt @@ -21,7 +21,10 @@ import java.util.regex.Pattern object LeakingProcessKillPattern { + private + val kotlinCompilerPattern = "(?:${Pattern.quote("-Dkotlin.environment.keepalive org.jetbrains.kotlin.daemon.KotlinCompileDaemon")})" + @JvmStatic fun generate(rootProjectDir: String): String = - "(?i)[/\\\\](java(?:\\.exe)?.+?(?:(?:-cp.+${Pattern.quote(rootProjectDir)}.+?(org\\.gradle\\.|[a-zA-Z]+))|(?:-classpath.+${Pattern.quote(rootProjectDir)}.+?${Pattern.quote("\\build\\")}.+?(org\\.gradle\\.|[a-zA-Z]+))|(?:-classpath.+${Pattern.quote(rootProjectDir)}.+?(play\\.core\\.server\\.NettyServer))).+)" + "(?i)[/\\\\](java(?:\\.exe)?.+?(?:(?:-cp.+${Pattern.quote(rootProjectDir)}.+?(org\\.gradle\\.|[a-zA-Z]+))|(?:-classpath.+${Pattern.quote(rootProjectDir)}.+?${Pattern.quote("\\build\\")}.+?(org\\.gradle\\.|[a-zA-Z]+))|(?:-classpath.+${Pattern.quote(rootProjectDir)}.+?(play\\.core\\.server\\.NettyServer))|$kotlinCompilerPattern).+)" } diff --git a/buildSrc/subprojects/cleanup/src/test/groovy/org/gradle/testing/LeakingProcessKillPatternTest.groovy b/buildSrc/subprojects/cleanup/src/test/groovy/org/gradle/testing/LeakingProcessKillPatternTest.groovy index 507373cb755ac..83c05c0c3a1a8 100644 --- a/buildSrc/subprojects/cleanup/src/test/groovy/org/gradle/testing/LeakingProcessKillPatternTest.groovy +++ b/buildSrc/subprojects/cleanup/src/test/groovy/org/gradle/testing/LeakingProcessKillPatternTest.groovy @@ -46,4 +46,13 @@ class LeakingProcessKillPatternTest extends Specification { expect: !(line =~ LeakingProcessKillPattern.generate(projectDir)).find() } + + def "matches kotlin compiler on linux"() { + def line = '11522 ? Sl 1:10 /home/paul/.sdkman/candidates/java/10.0.2-oracle/bin/java -cp /home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-compiler-embeddable/1.3.11/a8db6c14f8b8ed74aa11b8379f961587b639c571/kotlin-compiler-embeddable-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-reflect/1.3.11/aae7b33412715e9ed441934c4ffaad1bb80e9d36/kotlin-reflect-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.11/4cbc5922a54376018307a731162ccaf3ef851a39/kotlin-stdlib-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.3.11/1ef3a816aeacb9cd051b3ed37e2abf88910f1503/kotlin-script-runtime-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.11/d8b8e746e279f1c4f5e08bc14a96b82e6bb1de02/kotlin-stdlib-common-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar -Djava.awt.headless=true -Djava.rmi.server.hostname=127.0.0.1 -Xmx320m -Dkotlin.environment.keepalive org.jetbrains.kotlin.daemon.KotlinCompileDaemon --daemon-runFilesPath /home/paul/.kotlin/daemon --daemon-autoshutdownIdleSeconds=7200 --daemon-compilerClasspath /home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-compiler-embeddable/1.3.11/a8db6c14f8b8ed74aa11b8379f961587b639c571/kotlin-compiler-embeddable-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-reflect/1.3.11/aae7b33412715e9ed441934c4ffaad1bb80e9d36/kotlin-reflect-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.3.11/4cbc5922a54376018307a731162ccaf3ef851a39/kotlin-stdlib-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-script-runtime/1.3.11/1ef3a816aeacb9cd051b3ed37e2abf88910f1503/kotlin-script-runtime-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-common/1.3.11/d8b8e746e279f1c4f5e08bc14a96b82e6bb1de02/kotlin-stdlib-common-1.3.11.jar:/home/paul/.gradle/caches/modules-2/files-2.1/org.jetbrains/annotations/13.0/919f0dfe192fb4e063e7dacadee7f8bb9a2672a9/annotations-13.0.jar' + + def projectDir = "/home/paul/src/kotlin-dsl" + + expect: + (line =~ LeakingProcessKillPattern.generate(projectDir)).find() + } } diff --git a/buildSrc/subprojects/configuration/src/main/kotlin/org/gradle/gradlebuild/PublicApi.kt b/buildSrc/subprojects/configuration/src/main/kotlin/org/gradle/gradlebuild/PublicApi.kt index 34414ee4e54ca..beb3bc22214c6 100644 --- a/buildSrc/subprojects/configuration/src/main/kotlin/org/gradle/gradlebuild/PublicApi.kt +++ b/buildSrc/subprojects/configuration/src/main/kotlin/org/gradle/gradlebuild/PublicApi.kt @@ -33,6 +33,7 @@ object PublicApi { "org/gradle/testkit/**", "org/gradle/testing/**", "org/gradle/vcs/**", + "org/gradle/work/**", "org/gradle/workers/**") val excludes = listOf("**/internal/**") diff --git a/buildSrc/subprojects/kotlin-dsl/kotlin-dsl.gradle.kts b/buildSrc/subprojects/kotlin-dsl/kotlin-dsl.gradle.kts index ec4c394fd215b..27ff159957989 100644 --- a/buildSrc/subprojects/kotlin-dsl/kotlin-dsl.gradle.kts +++ b/buildSrc/subprojects/kotlin-dsl/kotlin-dsl.gradle.kts @@ -3,11 +3,12 @@ dependencies { implementation(project(":configuration")) implementation(project(":build")) - implementation(kotlin("gradle-plugin")) - implementation(kotlin("stdlib-jdk8")) - implementation(kotlin("reflect")) + api(kotlin("gradle-plugin")) + api(kotlin("stdlib-jdk8")) + api(kotlin("reflect")) + api(kotlin("compiler-embeddable")) - implementation("org.gradle.kotlin:gradle-kotlin-dsl-conventions:0.2.3") + implementation("org.gradle.kotlin:gradle-kotlin-dsl-conventions:0.3.0") implementation("com.gradle.publish:plugin-publish-plugin:0.10.0") implementation("com.thoughtworks.qdox:qdox:2.0-M9") diff --git a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/build/GradleApiParameterNamesTransform.kt b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/build/GradleApiParameterNamesTransform.kt deleted file mode 100644 index 49a818f2ad18d..0000000000000 --- a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/build/GradleApiParameterNamesTransform.kt +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package build - -import accessors.sourceSets -import org.gradle.gradlebuild.PublicApi - -import org.gradle.api.Project -import org.gradle.api.artifacts.transform.ArtifactTransform -import org.gradle.api.attributes.Attribute -import org.gradle.api.file.RelativePath - -import org.gradle.kotlin.dsl.* - -import org.gradle.kotlin.dsl.codegen.ParameterNamesSupplier -import org.gradle.kotlin.dsl.codegen.parameterNamesFor - -import org.gradle.kotlin.dsl.support.GradleApiMetadata - -import org.objectweb.asm.ClassReader -import org.objectweb.asm.ClassVisitor -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Opcodes.ASM6 -import org.objectweb.asm.Type - -import java.io.InputStream -import java.io.File -import java.util.Properties -import java.util.jar.JarEntry -import java.util.jar.JarFile -import java.util.zip.ZipOutputStream -import javax.inject.Inject - - -// TODO:kotlin-dsl dedupe, see MinifyPlugin -private -val minified = Attribute.of("minified", Boolean::class.javaObjectType) - - -fun Project.withCompileOnlyGradleApiModulesWithParameterNames(vararg gradleModuleNames: String) { - - val artifactType = Attribute.of("artifactType", String::class.java) - val jarWithGradleApiParameterNames = "jar-with-gradle-api-parameter-names" - - val gradleApiWithParameterNames by configurations.registering { - attributes { - attribute(artifactType, jarWithGradleApiParameterNames) - } - } - - dependencies { - registerTransform { - from.attribute(artifactType, "jar").attribute(minified, true) - to.attribute(artifactType, jarWithGradleApiParameterNames) - artifactTransform(GradleApiParameterNamesTransform::class) { - params(PublicApi.includes, PublicApi.excludes) - } - } - - for (gradleModuleName in gradleModuleNames) { - gradleApiWithParameterNames.name(project(gradleModuleName)) - } - } - - sourceSets { - "main" { - compileClasspath += gradleApiWithParameterNames.get() - } - } -} - - -internal -class GradleApiParameterNamesTransform @Inject constructor( - private val publicApiIncludes: List, - private val publicApiExcludes: List -) : ArtifactTransform() { - - override fun transform(input: File): MutableList = - if (input.name.startsWith("gradle-")) mutableListOf(outputFileFor(input).also { outputFile -> - transformGradleApiJar(input, outputFile) - }) - else mutableListOf(input) - - private - fun outputFileFor(input: File) = - outputDirectory.resolve("${input.nameWithoutExtension}-with-parameter-names.jar") - - private - fun transformGradleApiJar(inputFile: File, outputFile: File) { - JarFile(inputFile).use { inputJar -> - val gradleApiMetadata = gradleApiMetadataFor(inputFile.gradleModuleName, inputJar) - writingJar(outputFile) { zipOutputStream -> - transformGradleApiJarEntries(gradleApiMetadata, inputJar, zipOutputStream) - } - } - } - - private - fun writingJar(outputJarFile: File, action: (ZipOutputStream) -> Unit) { - outputJarFile.outputStream().buffered().use { fileStream -> - ZipOutputStream(fileStream).use(action) - } - } - - private - fun transformGradleApiJarEntries(gradleApiMetadata: GradleApiMetadata, inputJar: JarFile, zipOutputStream: ZipOutputStream) { - inputJar.entries().asSequence().filterNot { it.isDirectory }.forEach { entry -> - if (gradleApiMetadata.isGradleApi(entry)) inputJar.transformJarEntry(gradleApiMetadata, entry, zipOutputStream) - else inputJar.copyJarEntry(entry, zipOutputStream) - } - } - - private - fun gradleApiMetadataFor(moduleName: String, inputJar: JarFile): GradleApiMetadata { - val parameterNamesIndex = Properties().apply { - inputJar.getJarEntry("$moduleName-parameter-names.properties")?.let { entry -> - inputJar.getInputStream(entry).buffered().use { input -> load(input) } - } - } - return GradleApiMetadata( - publicApiIncludes, - publicApiExcludes, - { key: String -> parameterNamesIndex.getProperty(key, null)?.split(",") } - ) - } - - private - fun GradleApiMetadata.isGradleApi(entry: JarEntry) = - entry.name.endsWith(".class") - && !entry.name.endsWith("package-info.class") - && spec.isSatisfiedBy(RelativePath.parse(true, entry.name)) - - private - fun JarFile.transformJarEntry(gradleApiMetadata: GradleApiMetadata, entry: JarEntry, zipOutputStream: ZipOutputStream) { - getInputStream(entry).buffered().use { input -> - zipOutputStream.run { - putNextEntry(JarEntry(entry.name)) - write(transformClass(gradleApiMetadata, input)) - closeEntry() - } - } - } - - private - fun transformClass(gradleApiMetadata: GradleApiMetadata, input: InputStream): ByteArray { - val writer = ClassWriter(0) - val transformer = ParameterNamesClassVisitor(writer, gradleApiMetadata.parameterNamesSupplier) - ClassReader(input).accept(transformer, 0) - return writer.toByteArray() - } - - private - fun JarFile.copyJarEntry(entry: JarEntry, zipOutputStream: ZipOutputStream) { - getInputStream(entry).buffered().use { input -> - zipOutputStream.putNextEntry(JarEntry(entry.name)) - input.copyTo(zipOutputStream) - zipOutputStream.closeEntry() - } - } -} - - -private -val gradleModuleNameRegex = Regex("-\\d.*") - - -private -val File.gradleModuleName: String - get() = nameWithoutExtension.replace(gradleModuleNameRegex, "") - - -private -class ParameterNamesClassVisitor( - delegate: ClassVisitor, - private val parameterNamesSupplier: ParameterNamesSupplier -) : ClassVisitor(ASM6, delegate) { - - private - lateinit var typeName: String - - override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array?) { - typeName = name - super.visit(version, access, name, signature, superName, interfaces) - } - - override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor { - return super.visitMethod(access, name, desc, signature, exceptions).apply { - parameterNamesSupplier.parameterNamesForBinaryNames(typeName, name, desc)?.forEach { parameterName -> - visitParameter(parameterName, 0) - } - } - } - - private - fun ParameterNamesSupplier.parameterNamesForBinaryNames(typeName: String, methodName: String, methodDescriptor: String) = - parameterNamesFor( - typeBinaryNameFor(typeName), - methodName, - parameterTypesBinaryNamesFor(methodDescriptor) - ) - - private - fun typeBinaryNameFor(internalName: String): String = - Type.getObjectType(internalName).className - - private - fun parameterTypesBinaryNamesFor(methodDescriptor: String) = - Type.getArgumentTypes(methodDescriptor).map { it.className } -} diff --git a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/build/PatchKotlinCompilerEmbeddable.kt b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/build/PatchKotlinCompilerEmbeddable.kt index adc70f5a45398..b73f9cf2ffa20 100644 --- a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/build/PatchKotlinCompilerEmbeddable.kt +++ b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/build/PatchKotlinCompilerEmbeddable.kt @@ -17,16 +17,19 @@ package build import org.gradle.api.DefaultTask -import org.gradle.api.file.EmptyFileVisitor -import org.gradle.api.file.FileTree -import org.gradle.api.file.FileVisitDetails +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.RegularFileProperty import org.gradle.api.file.RelativePath +import org.gradle.api.provider.ListProperty import org.gradle.api.specs.Spec import org.gradle.api.specs.Specs import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputFile +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import org.gradle.api.internal.file.archive.ZipCopyAction.CONSTANT_TIME_FOR_ZIP_ENTRIES @@ -41,25 +44,27 @@ import org.gradle.kotlin.dsl.* @CacheableTask -open class PatchKotlinCompilerEmbeddable : DefaultTask() { +@Suppress("unused") +abstract class PatchKotlinCompilerEmbeddable : DefaultTask() { - @Input - val excludes = project.objects.listProperty() + @get:Input + abstract val excludes: ListProperty - @Classpath - val originalFiles = project.files() + @get:Classpath + abstract val originalFiles: ConfigurableFileCollection - @Classpath - val dependencies = project.files() + @get:Classpath + abstract val dependencies: ConfigurableFileCollection @Input val dependenciesIncludes = project.objects.mapProperty>() - @Classpath - lateinit var additionalFiles: FileTree + @get:InputFiles + @get:PathSensitive(PathSensitivity.NAME_ONLY) + abstract val additionalRootFiles: ConfigurableFileCollection - @OutputFile - val outputFile = project.objects.fileProperty() + @get:OutputFile + abstract val outputFile: RegularFileProperty @TaskAction @Suppress("unused") @@ -87,7 +92,7 @@ open class PatchKotlinCompilerEmbeddable : DefaultTask() { patchedJar.setComment(originalJar.comment) copyFromOriginalApplyingExcludes(originalJar, patchedJar) copyFromDependenciesApplyingIncludes(patchedJar) - copyAdditionalFiles(patchedJar) + copyAdditionalRootFiles(patchedJar) } private @@ -114,19 +119,17 @@ open class PatchKotlinCompilerEmbeddable : DefaultTask() { } private - fun copyAdditionalFiles(patchedJar: ZipOutputStream) = - additionalFiles.visit(object : EmptyFileVisitor() { - override fun visitFile(fileDetails: FileVisitDetails) { - patchedJar.putNextEntry(ZipEntry(fileDetails.relativePath.pathString).apply { - time = CONSTANT_TIME_FOR_ZIP_ENTRIES - size = fileDetails.file.length() - }) - fileDetails.file.inputStream().buffered().use { input -> - input.copyTo(patchedJar) - } - patchedJar.closeEntry() + fun copyAdditionalRootFiles(patchedJar: ZipOutputStream) = + additionalRootFiles.forEach { additionalRootFile -> + patchedJar.putNextEntry(ZipEntry(additionalRootFile.name).apply { + time = CONSTANT_TIME_FOR_ZIP_ENTRIES + size = additionalRootFile.length() + }) + additionalRootFile.inputStream().buffered().use { input -> + input.copyTo(patchedJar) } - }) + patchedJar.closeEntry() + } private fun copyEntry(sourceJar: ZipFile, sourceEntry: ZipEntry, destinationJar: ZipOutputStream) { diff --git a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/CodeGenerationTask.kt b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/CodeGenerationTask.kt new file mode 100644 index 0000000000000..0f84c337f1920 --- /dev/null +++ b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/CodeGenerationTask.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package codegen + +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +import java.io.File + + +abstract class CodeGenerationTask : DefaultTask() { + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + protected + abstract fun File.writeFiles(): Unit + + @Suppress("unused") + @TaskAction + fun run() { + outputDir.get().asFile.apply { + recreate() + writeFiles() + } + } + + protected + fun File.writeFile(relativePath: String, text: String) { + resolve(relativePath).apply { + parentFile.mkdirs() + writeText(text) + } + } + + private + fun File.recreate() { + deleteRecursively() + mkdirs() + } +} + + +internal +const val licenseHeader = """/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */""" diff --git a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/GenerateKotlinDependencyExtensions.kt b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/GenerateKotlinDependencyExtensions.kt index 3f609a42c4da9..a5e6ed5731b80 100644 --- a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/GenerateKotlinDependencyExtensions.kt +++ b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/GenerateKotlinDependencyExtensions.kt @@ -16,30 +16,44 @@ package codegen -import org.gradle.api.DefaultTask - +import org.gradle.api.provider.Property import org.gradle.api.tasks.Input -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction import java.io.File -open class GenerateKotlinDependencyExtensions : DefaultTask() { - - @get:OutputFile - var outputFile: File? = null +@Suppress("unused") +abstract class GenerateKotlinDependencyExtensions : CodeGenerationTask() { @get:Input - var embeddedKotlinVersion: String? = null + abstract val embeddedKotlinVersion: Property @get:Input - var kotlinDslPluginsVersion: String? = null + abstract val kotlinDslPluginsVersion: Property + + override fun File.writeFiles() { + + val kotlinDslPluginsVersion = kotlinDslPluginsVersion.get() + val embeddedKotlinVersion = embeddedKotlinVersion.get() - @Suppress("unused") - @TaskAction - fun generate() { - outputFile!!.writeText( + // IMPORTANT: kotlinDslPluginsVersion should NOT be made a `const` to avoid inlining + writeFile( + "org/gradle/kotlin/dsl/support/KotlinDslPlugins.kt", + """$licenseHeader + +package org.gradle.kotlin.dsl.support + + +/** + * Expected version of the `kotlin-dsl` plugin for the running Gradle version. + */ +@Suppress("unused") +val expectedKotlinDslPluginsVersion: String + get() = "$kotlinDslPluginsVersion" +""") + + writeFile( + "org/gradle/kotlin/dsl/KotlinDependencyExtensions.kt", """$licenseHeader package org.gradle.kotlin.dsl @@ -93,8 +107,6 @@ fun PluginDependenciesSpec.kotlin(module: String): PluginDependencySpec = * * Equivalent to `id("org.gradle.kotlin.embedded-kotlin") version "$kotlinDslPluginsVersion"` * - * You can also use `` `embedded-kotlin` version "$kotlinDslPluginsVersion" `` if you want to use a different version. - * * @see org.gradle.kotlin.dsl.plugins.embedded.EmbeddedKotlinPlugin */ val PluginDependenciesSpec.`embedded-kotlin`: PluginDependencySpec @@ -106,8 +118,6 @@ val PluginDependenciesSpec.`embedded-kotlin`: PluginDependencySpec * * Equivalent to `id("org.gradle.kotlin.kotlin-dsl") version "$kotlinDslPluginsVersion"` * - * You can also use `` `kotlin-dsl` version "$kotlinDslPluginsVersion" `` if you want to use a different version. - * * @see org.gradle.kotlin.dsl.plugins.dsl.KotlinDslPlugin */ val PluginDependenciesSpec.`kotlin-dsl`: PluginDependencySpec @@ -119,8 +129,6 @@ val PluginDependenciesSpec.`kotlin-dsl`: PluginDependencySpec * * Equivalent to `id("org.gradle.kotlin.kotlin-dsl.base") version "$kotlinDslPluginsVersion"` * - * You can also use `` `kotlin-dsl-base` version "$kotlinDslPluginsVersion" `` if you want to use a different version. - * * @see org.gradle.kotlin.dsl.plugins.base.KotlinDslBasePlugin */ val PluginDependenciesSpec.`kotlin-dsl-base`: PluginDependencySpec @@ -132,8 +140,6 @@ val PluginDependenciesSpec.`kotlin-dsl-base`: PluginDependencySpec * * Equivalent to `id("org.gradle.kotlin.kotlin-dsl.precompiled-script-plugins") version "$kotlinDslPluginsVersion"` * - * You can also use `` `kotlin-dsl-precompiled-script-plugins` version "$kotlinDslPluginsVersion" `` if you want to use a different version. - * * @see org.gradle.kotlin.dsl.plugins.precompiled.PrecompiledScriptPlugins */ val PluginDependenciesSpec.`kotlin-dsl-precompiled-script-plugins`: PluginDependencySpec @@ -141,21 +147,3 @@ val PluginDependenciesSpec.`kotlin-dsl-precompiled-script-plugins`: PluginDepend """) } } - - -internal -const val licenseHeader = """/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */""" diff --git a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/GenerateKotlinDslPluginsExtensions.kt b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/GenerateKotlinDslPluginsExtensions.kt new file mode 100644 index 0000000000000..18f37bfe0250a --- /dev/null +++ b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/codegen/GenerateKotlinDslPluginsExtensions.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package codegen + +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input + +import java.io.File + + +@Suppress("unused") +abstract class GenerateKotlinDslPluginsExtensions : CodeGenerationTask() { + + @get:Input + abstract val kotlinDslPluginsVersion: Property + + override fun File.writeFiles() { + writeFile( + "org/gradle/kotlin/dsl/plugins/Version.kt", + """$licenseHeader +package org.gradle.kotlin.dsl.plugins + + +internal +val appliedKotlinDslPluginsVersion = "${kotlinDslPluginsVersion.get()}" +""") + } +} diff --git a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/parser/KotlinSourceParser.kt b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/parser/KotlinSourceParser.kt new file mode 100644 index 0000000000000..96a1b3a119b86 --- /dev/null +++ b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/parser/KotlinSourceParser.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package parser + +import org.gradle.internal.jvm.Jvm + +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoots +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.messages.MessageRenderer +import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots +import org.jetbrains.kotlin.com.intellij.openapi.Disposable +import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.utils.PathUtil + +import java.io.File + + +class KotlinSourceParser { + + private + val messageCollector: MessageCollector + get() = PrintingMessageCollector(System.out, MessageRenderer.PLAIN_RELATIVE_PATHS, false) + + fun mapParsedKotlinFiles(vararg sourceRoots: File, block: (KtFile) -> T): List = + withParsedKotlinSource(sourceRoots.toList()) { ktFiles -> + ktFiles.map(block) + } + + private + fun withParsedKotlinSource(sourceRoots: List, block: (List) -> T) = withRootDisposable { + + val configuration = CompilerConfiguration().apply { + + put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector) + put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, false) + put(CommonConfigurationKeys.MODULE_NAME, "parser") + + addJvmClasspathRoots(PathUtil.getJdkClassesRoots(Jvm.current().javaHome)) + addKotlinSourceRoots(sourceRoots.map { it.canonicalPath }) + } + val environment = KotlinCoreEnvironment.createForProduction(this, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES) + environment.getSourceFiles().let(block) + } +} + + +internal +inline fun withRootDisposable(action: Disposable.() -> T): T { + val rootDisposable = Disposer.newDisposable() + try { + return action(rootDisposable) + } finally { + Disposer.dispose(rootDisposable) + } +} diff --git a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/plugins/kotlin-dsl-module.gradle.kts b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/plugins/kotlin-dsl-module.gradle.kts index 5f813ac1fcfc3..b8a289cf4e792 100644 --- a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/plugins/kotlin-dsl-module.gradle.kts +++ b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/plugins/kotlin-dsl-module.gradle.kts @@ -29,9 +29,8 @@ plugins { } // including all sources -val main by sourceSets -tasks.named("jar") { - from(main.allSource) +tasks.jar { + from(sourceSets.main.get().allSource) manifest.attributes.apply { put("Implementation-Title", "Gradle Kotlin DSL (${project.name})") put("Implementation-Version", archiveVersion.get()) diff --git a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/plugins/kotlin-dsl-plugin-bundle.gradle.kts b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/plugins/kotlin-dsl-plugin-bundle.gradle.kts index 3e77884e4eb73..82ba52227d762 100644 --- a/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/plugins/kotlin-dsl-plugin-bundle.gradle.kts +++ b/buildSrc/subprojects/kotlin-dsl/src/main/kotlin/plugins/kotlin-dsl-plugin-bundle.gradle.kts @@ -44,7 +44,7 @@ afterEvaluate { tasks { - "validateTaskProperties"(ValidateTaskProperties::class) { + validateTaskProperties { failOnWarning = true enableStricterValidation = true } diff --git a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/MinifyTransform.kt b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/Minify.kt similarity index 62% rename from buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/MinifyTransform.kt rename to buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/Minify.kt index cfbe1eff78028..5098cd5f2f5b7 100644 --- a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/MinifyTransform.kt +++ b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/Minify.kt @@ -17,30 +17,49 @@ package org.gradle.gradlebuild.packaging import com.google.common.io.Files -import org.gradle.api.artifacts.transform.ArtifactTransform +import org.gradle.api.artifacts.transform.CacheableTransform +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import java.io.BufferedOutputStream import java.io.File import java.io.FileOutputStream import java.util.jar.JarFile import java.util.jar.JarOutputStream -import javax.inject.Inject -open class MinifyTransform @Inject constructor( - private val keepClassesByArtifact: Map> -) : ArtifactTransform() { +@CacheableTransform +abstract class Minify : TransformAction { - override fun transform(artifact: File) = - keepClassesByArtifact.asSequence() - .firstOrNull { (key, _) -> artifact.name.startsWith(key) } - ?.value?.let { keepClasses -> listOf(minify(artifact, keepClasses)) } - ?: listOf(artifact) + interface Parameters : TransformParameters { + @get:Input + var keepClassesByArtifact: Map> + } + + @get:PathSensitive(PathSensitivity.NAME_ONLY) + @get:InputArtifact + abstract val artifact: File + + override fun transform(outputs: TransformOutputs) { + for (entry in parameters.keepClassesByArtifact) { + if (artifact.name.startsWith(entry.key)) { + val nameWithoutExtension = Files.getNameWithoutExtension(artifact.path) + minify(artifact, entry.value, outputs.file("$nameWithoutExtension-min.jar")) + return + } + } + outputs.file(artifact) + } private - fun minify(artifact: File, keepClasses: Set): File { - val jarFile = outputDirectory.resolve("${Files.getNameWithoutExtension(artifact.path)}-min.jar") - val classesDir = outputDirectory.resolve("classes") - val manifestFile = outputDirectory.resolve("MANIFEST.MF") + fun minify(artifact: File, keepClasses: Set, jarFile: File): File { + val tempDirectory = java.nio.file.Files.createTempDirectory(jarFile.name).toFile() + val classesDir = tempDirectory.resolve("classes") + val manifestFile = tempDirectory.resolve("MANIFEST.MF") val classGraph = JarAnalyzer("", keepClasses, keepClasses, setOf()).analyze(artifact, classesDir, manifestFile) createJar(classGraph, classesDir, manifestFile, jarFile) diff --git a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/MinifyPlugin.kt b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/MinifyPlugin.kt index 1196525ff8403..25136dcaba5fd 100644 --- a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/MinifyPlugin.kt +++ b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/MinifyPlugin.kt @@ -51,7 +51,7 @@ open class MinifyPlugin : Plugin { artifactTypes.getByName("jar") { attributes.attribute(minified, java.lang.Boolean.FALSE) } - registerTransform { + registerTransform(Minify::class) { /* * TODO Why do I have to add artifactType=jar here? According to * the declaration above, it's the only artifact type for which @@ -60,8 +60,8 @@ open class MinifyPlugin : Plugin { */ from.attribute(minified, false).attribute(artifactType, "jar") to.attribute(minified, true).attribute(artifactType, "jar") - artifactTransform(MinifyTransform::class) { - params(keepPatterns) + parameters { + keepClassesByArtifact = keepPatterns } } } diff --git a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadeClasses.kt b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadeClasses.kt new file mode 100644 index 0000000000000..d9deb179d143e --- /dev/null +++ b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadeClasses.kt @@ -0,0 +1,122 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.gradlebuild.packaging + +import com.google.gson.Gson +import org.gradle.api.artifacts.transform.CacheableTransform +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import java.io.File + + +private +const val classTreeFileName = "classTree.json" + + +private +const val entryPointsFileName = "entryPoints.json" + + +private +const val relocatedClassesDirName = "classes" + + +private +const val manifestFileName = "MANIFEST.MF" + + +@CacheableTransform +abstract class ShadeClasses : TransformAction { + + interface Parameters : TransformParameters { + @get:Input + var shadowPackage: String + @get:Input + var keepPackages: Set + @get:Input + var unshadedPackages: Set + @get:Input + var ignoredPackages: Set + } + + @get:Classpath + @get:InputArtifact + abstract val input: File + + override fun transform(outputs: TransformOutputs) { + val outputDirectory = outputs.dir("shadedClasses") + val classesDir = outputDirectory.resolve(relocatedClassesDirName) + classesDir.mkdir() + val manifestFile = outputDirectory.resolve(manifestFileName) + + val classGraph = JarAnalyzer(parameters.shadowPackage, parameters.keepPackages, parameters.unshadedPackages, parameters.ignoredPackages).analyze(input, classesDir, manifestFile) + + outputDirectory.resolve(classTreeFileName).bufferedWriter().use { + Gson().toJson(classGraph.getDependencies(), it) + } + outputDirectory.resolve(entryPointsFileName).bufferedWriter().use { + Gson().toJson(classGraph.entryPoints.map { it.outputClassFilename }, it) + } + } +} + + +abstract class FindClassTrees : TransformAction { + @get:InputArtifact + abstract val input: File + + override fun transform(outputs: TransformOutputs) { + outputs.file(input.resolve(classTreeFileName)) + } +} + + +abstract class FindEntryPoints : TransformAction { + @get:InputArtifact + abstract val input: File + + override fun transform(outputs: TransformOutputs) { + outputs.file(input.resolve(entryPointsFileName)) + } +} + + +abstract class FindRelocatedClasses : TransformAction { + @get:InputArtifact + abstract val input: File + + override fun transform(outputs: TransformOutputs) { + outputs.dir(input.resolve(relocatedClassesDirName)) + } +} + + +abstract class FindManifests : TransformAction { + @get:InputArtifact + abstract val input: File + + override fun transform(outputs: TransformOutputs) { + val manifest = input.resolve(manifestFileName) + if (manifest.exists()) { + outputs.file(manifest) + } + } +} diff --git a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadeClassesTransform.kt b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadeClassesTransform.kt deleted file mode 100644 index f1a74eb075f9b..0000000000000 --- a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadeClassesTransform.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.gradlebuild.packaging - -import com.google.gson.Gson -import org.gradle.api.artifacts.transform.ArtifactTransform -import java.io.File -import javax.inject.Inject - - -private -const val classTreeFileName = "classTree.json" - - -private -const val entryPointsFileName = "entryPoints.json" - - -private -const val relocatedClassesDirName = "classes" - - -private -const val manifestFileName = "MANIFEST.MF" - - -open class ShadeClassesTransform @Inject constructor( - private val shadowPackage: String, - private val keepPackages: Set, - private val unshadedPackages: Set, - private val ignorePackages: Set -) : ArtifactTransform() { - - override fun transform(input: File): List { - val classesDir = outputDirectory.resolve(relocatedClassesDirName) - classesDir.mkdir() - val manifestFile = outputDirectory.resolve(manifestFileName) - - val classGraph = JarAnalyzer(shadowPackage, keepPackages, unshadedPackages, ignorePackages).analyze(input, classesDir, manifestFile) - - outputDirectory.resolve(classTreeFileName).bufferedWriter().use { - Gson().toJson(classGraph.getDependencies(), it) - } - outputDirectory.resolve(entryPointsFileName).bufferedWriter().use { - Gson().toJson(classGraph.entryPoints.map { it.outputClassFilename }, it) - } - - return listOf(outputDirectory) - } -} - - -open class FindClassTrees : ArtifactTransform() { - override fun transform(input: File): List { - return listOf(input.resolve(classTreeFileName)) - } -} - - -open class FindEntryPoints : ArtifactTransform() { - override fun transform(input: File): List { - return listOf(input.resolve(entryPointsFileName)) - } -} - - -open class FindRelocatedClasses : ArtifactTransform() { - override fun transform(input: File): List { - return listOf(input.resolve(relocatedClassesDirName)) - } -} - - -open class FindManifests : ArtifactTransform() { - override fun transform(input: File): List { - val manifest = input.resolve(manifestFileName) - return listOf(manifest).filter { it.exists() } - } -} diff --git a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadedJar.kt b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadedJar.kt index a484a8cec49cd..c3f33d1fb9931 100644 --- a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadedJar.kt +++ b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadedJar.kt @@ -72,7 +72,7 @@ open class ShadedJar : DefaultTask() { val classesToInclude = mutableSetOf() - val queue: Queue = ArrayDeque() + val queue: Queue = ArrayDeque() queue.addAll(entryPoints) while (!queue.isEmpty()) { val className = queue.remove() @@ -120,7 +120,7 @@ internal fun buildClassTrees(individualClassTrees: List>>): Map> = individualClassTrees.flatMap { it.entries } .groupingBy { it.key } - .aggregate>, String, Set> { _, accumulator: Set?, element: Map.Entry>, first -> + .aggregate { _, accumulator: Set?, element: Map.Entry>, first -> if (first) { element.value.toSet() } else { diff --git a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadedJarPlugin.kt b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadedJarPlugin.kt index e69ef3cf081bc..4a4d36acb38fe 100644 --- a/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadedJarPlugin.kt +++ b/buildSrc/subprojects/packaging/src/main/kotlin/org/gradle/gradlebuild/packaging/ShadedJarPlugin.kt @@ -17,13 +17,9 @@ package org.gradle.gradlebuild.packaging import accessors.base -import org.gradle.api.Action -import org.gradle.api.ActionConfiguration import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.artifacts.Configuration -import org.gradle.api.artifacts.dsl.DependencyHandler -import org.gradle.api.artifacts.transform.ArtifactTransform import org.gradle.api.attributes.Usage import org.gradle.api.model.ObjectFactory import org.gradle.api.tasks.Copy @@ -33,7 +29,6 @@ import org.gradle.gradlebuild.packaging.Attributes.artifactType import org.gradle.gradlebuild.packaging.Attributes.minified import org.gradle.kotlin.dsl.* import java.io.File -import java.lang.IllegalArgumentException private @@ -85,27 +80,37 @@ open class ShadedJarPlugin : Plugin { fun Project.registerTransforms(shadedJarExtension: ShadedJarExtension) { afterEvaluate { dependencies { - registerTransform { + registerTransform(ShadeClasses::class) { from .attribute(artifactType, "jar") .attribute(minified, true) to.attribute(artifactType, relocatedClassesAndAnalysisType) - artifactTransform(ShadeClassesTransform::class) { - params( - "org.gradle.internal.impldep", - shadedJarExtension.keepPackages.get(), - shadedJarExtension.unshadedPackages.get(), - shadedJarExtension.ignoredPackages.get() - ) + parameters { + shadowPackage = "org.gradle.internal.impldep" + keepPackages = shadedJarExtension.keepPackages.get() + unshadedPackages = shadedJarExtension.unshadedPackages.get() + ignoredPackages = shadedJarExtension.ignoredPackages.get() } } } } dependencies { - registerArtifactTypeTransform(relocatedClassesAndAnalysisType, relocatedClassesType) - registerArtifactTypeTransform(relocatedClassesAndAnalysisType, entryPointsType) - registerArtifactTypeTransform(relocatedClassesAndAnalysisType, classTreesType) - registerArtifactTypeTransform(relocatedClassesAndAnalysisType, manifestsType) + registerTransform(FindRelocatedClasses::class) { + from.attribute(artifactType, relocatedClassesAndAnalysisType) + to.attribute(artifactType, relocatedClassesType) + } + registerTransform(FindEntryPoints::class) { + from.attribute(artifactType, relocatedClassesAndAnalysisType) + to.attribute(artifactType, entryPointsType) + } + registerTransform(FindClassTrees::class) { + from.attribute(artifactType, relocatedClassesAndAnalysisType) + to.attribute(artifactType, classTreesType) + } + registerTransform(FindManifests::class) { + from.attribute(artifactType, relocatedClassesAndAnalysisType) + to.attribute(artifactType, manifestsType) + } } } @@ -173,14 +178,6 @@ open class ShadedJarPlugin : Plugin { fun Configuration.artifactViewForType(artifactTypeName: String) = incoming.artifactView { attributes.attribute(artifactType, artifactTypeName) }.files - - private - inline fun DependencyHandler.registerArtifactTypeTransform(fromType: String, toType: String, action: Action = Action {}) = - registerTransform { - from.attribute(artifactType, fromType) - to.attribute(artifactType, toType) - artifactTransform(T::class, action) - } } diff --git a/buildSrc/subprojects/performance/src/main/groovy/org/gradle/testing/DistributedPerformanceTest.groovy b/buildSrc/subprojects/performance/src/main/groovy/org/gradle/testing/DistributedPerformanceTest.groovy index f07edd3b40121..d227adcf28a0d 100644 --- a/buildSrc/subprojects/performance/src/main/groovy/org/gradle/testing/DistributedPerformanceTest.groovy +++ b/buildSrc/subprojects/performance/src/main/groovy/org/gradle/testing/DistributedPerformanceTest.groovy @@ -84,7 +84,7 @@ class DistributedPerformanceTest extends ReportGenerationPerformanceTest { private RESTClient client - private Map scheduledBuilds = [:] + protected Map scheduledBuilds = [:] private Map finishedBuilds = [:] @@ -134,13 +134,17 @@ class DistributedPerformanceTest extends ReportGenerationPerformanceTest { } @Override + @TypeChecked(TypeCheckingMode.SKIP) protected List generateResultsForReport() { finishedBuilds.collect { workerBuildId, scenarioResult -> new ScenarioBuildResultData( teamCityBuildId: workerBuildId, scenarioName: scheduledBuilds.get(workerBuildId).id, - webUrl: scenarioResult.buildResult.webUrl.toString(), - status: scenarioResult.buildResult.status.toString(), + scenarioClass: scenarioResult.testSuite.name, + webUrl: scenarioResult.buildResult.webUrl, + status: scenarioResult.buildResult.status, + agentName: scenarioResult.buildResult.agent.name, + agentUrl: scenarioResult.buildResult.agent.webUrl, testFailure: collectFailures(scenarioResult.testSuite)) } } diff --git a/buildSrc/subprojects/performance/src/main/groovy/org/gradle/testing/ReportGenerationPerformanceTest.groovy b/buildSrc/subprojects/performance/src/main/groovy/org/gradle/testing/ReportGenerationPerformanceTest.groovy index 8ad84f3354533..3a2cde6b73b25 100644 --- a/buildSrc/subprojects/performance/src/main/groovy/org/gradle/testing/ReportGenerationPerformanceTest.groovy +++ b/buildSrc/subprojects/performance/src/main/groovy/org/gradle/testing/ReportGenerationPerformanceTest.groovy @@ -51,6 +51,7 @@ abstract class ReportGenerationPerformanceTest extends PerformanceTest { spec.args(reportDir.path, resultJson.path, getProject().getName()) spec.systemProperties(databaseParameters) spec.systemProperty("org.gradle.performance.execution.channel", channel) + spec.systemProperty("githubToken", project.findProperty("githubToken")) spec.setClasspath(ReportGenerationPerformanceTest.this.getClasspath()) } }) @@ -76,9 +77,12 @@ abstract class ReportGenerationPerformanceTest extends PerformanceTest { static class ScenarioBuildResultData { String teamCityBuildId String scenarioName + String scenarioClass String webUrl String testFailure // SUCCESS/FAILURE/UNKNOWN String status + String agentName + String agentUrl } } diff --git a/buildSrc/subprojects/plugins/src/main/kotlin/gradlebuild/api-parameter-names-index.gradle.kts b/buildSrc/subprojects/plugins/src/main/kotlin/gradlebuild/api-parameter-names-index.gradle.kts index 0330e3a049d7e..a6b5b1b353276 100644 --- a/buildSrc/subprojects/plugins/src/main/kotlin/gradlebuild/api-parameter-names-index.gradle.kts +++ b/buildSrc/subprojects/plugins/src/main/kotlin/gradlebuild/api-parameter-names-index.gradle.kts @@ -35,7 +35,12 @@ val parameterNamesIndex by tasks.registering(ParameterNamesIndex::class) { if (file("src/main/groovy").isDirectory) { classpath.from(tasks.named("compileGroovy")) } - destinationFile.set(gradlebuildJava.generatedResourcesDir.resolve("${base.archivesBaseName}-parameter-names.properties")) + destinationFile.set( + gradlebuildJava.generatedResourcesDir.resolve("${base.archivesBaseName}-parameter-names.properties") + ) } -main.output.dir(gradlebuildJava.generatedResourcesDir, "builtBy" to parameterNamesIndex) +main.output.dir( + gradlebuildJava.generatedResourcesDir, + "builtBy" to parameterNamesIndex +) diff --git a/buildSrc/subprojects/plugins/src/main/kotlin/gradlebuild/publish-public-libraries.gradle.kts b/buildSrc/subprojects/plugins/src/main/kotlin/gradlebuild/publish-public-libraries.gradle.kts index d037e0ade76c3..ba353b1725d4b 100644 --- a/buildSrc/subprojects/plugins/src/main/kotlin/gradlebuild/publish-public-libraries.gradle.kts +++ b/buildSrc/subprojects/plugins/src/main/kotlin/gradlebuild/publish-public-libraries.gradle.kts @@ -27,7 +27,7 @@ import org.gradle.plugins.publish.createArtifactPattern import java.util.* plugins { - `maven` + maven } val generatePom = tasks.register("generatePom", GeneratePom::class.java) diff --git a/buildSrc/subprojects/plugins/src/main/kotlin/org/gradle/gradlebuild/unittestandcompile/UnitTestAndCompilePlugin.kt b/buildSrc/subprojects/plugins/src/main/kotlin/org/gradle/gradlebuild/unittestandcompile/UnitTestAndCompilePlugin.kt index 2c11a49ea9b41..d74946bcd21f8 100644 --- a/buildSrc/subprojects/plugins/src/main/kotlin/org/gradle/gradlebuild/unittestandcompile/UnitTestAndCompilePlugin.kt +++ b/buildSrc/subprojects/plugins/src/main/kotlin/org/gradle/gradlebuild/unittestandcompile/UnitTestAndCompilePlugin.kt @@ -25,7 +25,7 @@ import org.gradle.api.JavaVersion import org.gradle.api.Named import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.internal.tasks.testing.junit.result.TestResultSerializer +import org.gradle.api.plugins.JavaPluginConvention import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.compile.AbstractCompile import org.gradle.api.tasks.compile.CompileOptions @@ -41,10 +41,9 @@ import org.gradle.plugins.ide.idea.IdeaPlugin import org.gradle.plugins.ide.idea.model.IdeaModel import org.gradle.process.CommandLineArgumentProvider import testLibrary -import java.lang.IllegalStateException import java.util.concurrent.Callable -import java.util.concurrent.atomic.AtomicInteger import java.util.jar.Attributes +import kotlin.reflect.full.declaredFunctions enum class ModuleType(val compatibility: JavaVersion) { @@ -99,9 +98,6 @@ const val tooManyTestFailuresThreshold = 10 class UnitTestAndCompilePlugin : Plugin { - private - val allTestFailuresCount = AtomicInteger(0) - override fun apply(project: Project): Unit = project.run { apply(plugin = "groovy") @@ -199,19 +195,6 @@ class UnitTestAndCompilePlugin : Plugin { } } - private - fun Test.getPreviousFailedTestClasses(): Set = TestResultSerializer(binResultsDir).let { serializer -> - val previousFailedTestClasses = mutableSetOf() - serializer.read { - if (failuresCount > 0) { - allTestFailuresCount.addAndGet(failuresCount) - previousFailedTestClasses.add(className) - } - } - - previousFailedTestClasses - } - private fun Test.configureJvmForTest() { val javaInstallationForTest = project.rootProject.availableJavaInstallations.javaInstallationForTest @@ -232,20 +215,6 @@ class UnitTestAndCompilePlugin : Plugin { inputs.property("javaInstallation", Callable { javaInstallationForTest.vendorAndMajorVersion }) } - private - fun Test.onlyRunPreviousFailedClassesIfNecessary() { - if (project.stringPropertyOrEmpty("onlyPreviousFailedTestClasses").toBoolean()) { - val previousFailedClasses = getPreviousFailedTestClasses() - if (allTestFailuresCount.get() > tooManyTestFailuresThreshold) { - throw IllegalStateException("Too many failures (${allTestFailuresCount.get()}) in first run!") - } else if (previousFailedClasses.isEmpty()) { - enabled = false - } else { - previousFailedClasses.forEach { filter.includeTestsMatching(it) } - } - } - } - private fun Project.configureTests() { tasks.withType().configureEach { @@ -253,8 +222,6 @@ class UnitTestAndCompilePlugin : Plugin { configureJvmForTest() - onlyRunPreviousFailedClassesIfNecessary() - doFirst { if (BuildEnvironment.isCiServer) { logger.lifecycle("maxParallelForks for '$path' is $maxParallelForks") @@ -307,6 +274,7 @@ open class UnitTestAndCompileExtension(val project: Project) { field = value!! project.java.targetCompatibility = value.compatibility project.java.sourceCompatibility = value.compatibility + project.java.disableAutoTargetJvmGradle53() } init { @@ -317,3 +285,11 @@ open class UnitTestAndCompileExtension(val project: Project) { } } } + + +fun JavaPluginConvention.disableAutoTargetJvmGradle53() { + val function = JavaPluginConvention::class.declaredFunctions.find { it.name == "disableAutoTargetJvm" } + function?.also { + it.call(this) + } +} diff --git a/buildSrc/subprojects/profiling/profiling.gradle.kts b/buildSrc/subprojects/profiling/profiling.gradle.kts index f2f31ff7deb9d..84b74a6b89003 100644 --- a/buildSrc/subprojects/profiling/profiling.gradle.kts +++ b/buildSrc/subprojects/profiling/profiling.gradle.kts @@ -1,7 +1,7 @@ dependencies { implementation("me.champeau.gradle:jmh-gradle-plugin:0.4.8") implementation("org.jsoup:jsoup:1.11.3") - implementation("com.gradle:build-scan-plugin:2.1") + implementation("com.gradle:build-scan-plugin:2.2.1") implementation(project(":configuration")) implementation(project(":kotlinDsl")) implementation(project(":plugins")) diff --git a/buildSrc/subprojects/profiling/src/main/kotlin/gradlebuild/jmh.gradle.kts b/buildSrc/subprojects/profiling/src/main/kotlin/gradlebuild/jmh.gradle.kts index 495069b114405..ad595d66c13ef 100644 --- a/buildSrc/subprojects/profiling/src/main/kotlin/gradlebuild/jmh.gradle.kts +++ b/buildSrc/subprojects/profiling/src/main/kotlin/gradlebuild/jmh.gradle.kts @@ -16,20 +16,17 @@ package gradlebuild -import me.champeau.gradle.JMHPluginExtension - plugins { id("me.champeau.gradle.jmh") } configurations { - - getByName("jmhImplementation") { - extendsFrom(configurations["implementation"]) + jmhImplementation { + extendsFrom(configurations.implementation.get()) } } -configure { +jmh { isIncludeTests = false resultFormat = "CSV" } diff --git a/buildSrc/subprojects/profiling/src/main/kotlin/org/gradle/gradlebuild/profiling/buildscan/BuildScanPlugin.kt b/buildSrc/subprojects/profiling/src/main/kotlin/org/gradle/gradlebuild/profiling/buildscan/BuildScanPlugin.kt index 2fc034e771cad..93d7aefb5a4bc 100644 --- a/buildSrc/subprojects/profiling/src/main/kotlin/org/gradle/gradlebuild/profiling/buildscan/BuildScanPlugin.kt +++ b/buildSrc/subprojects/profiling/src/main/kotlin/org/gradle/gradlebuild/profiling/buildscan/BuildScanPlugin.kt @@ -28,19 +28,15 @@ import org.gradle.build.docs.CacheableAsciidoctorTask import org.gradle.gradlebuild.BuildEnvironment.isCiServer import org.gradle.gradlebuild.BuildEnvironment.isJenkins import org.gradle.gradlebuild.BuildEnvironment.isTravis -import org.gradle.internal.classloader.ClassLoaderHierarchyHasher -import org.gradle.kotlin.dsl.apply -import org.gradle.kotlin.dsl.support.serviceOf -import org.gradle.kotlin.dsl.the +import org.gradle.kotlin.dsl.* import org.jsoup.Jsoup import org.jsoup.parser.Parser import java.net.URLEncoder +import java.util.concurrent.atomic.AtomicBoolean import kotlin.collections.component1 import kotlin.collections.component2 import kotlin.collections.filter import kotlin.collections.forEach -import org.gradle.kotlin.dsl.* -import java.util.concurrent.atomic.AtomicBoolean const val serverUrl = "https://e.grdev.net" @@ -261,20 +257,6 @@ open class BuildScanPlugin : Plugin { fun Project.extractBuildCacheData() { if (gradle.startParameter.isBuildCacheEnabled) { buildScan.tag("CACHED") - - val tasksToInvestigate = System.getProperty("cache.investigate.tasks", ":baseServices:classpathManifest") - .split(",") - - gradle.taskGraph.whenReady { - buildScan.buildFinished { - gradle.taskGraph.allTasks - .filter { it.state.executed && it.path in tasksToInvestigate } - .forEach { task -> - val hasher = gradle.serviceOf() - Visitor(buildScan, hasher, task).visit(task::class.java.classLoader) - } - } - } } } diff --git a/buildSrc/subprojects/profiling/src/main/kotlin/org/gradle/gradlebuild/profiling/buildscan/Visitor.kt b/buildSrc/subprojects/profiling/src/main/kotlin/org/gradle/gradlebuild/profiling/buildscan/Visitor.kt deleted file mode 100644 index 03e5f8f904107..0000000000000 --- a/buildSrc/subprojects/profiling/src/main/kotlin/org/gradle/gradlebuild/profiling/buildscan/Visitor.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.gradle.gradlebuild.profiling.buildscan - -import com.gradle.scan.plugin.BuildScanExtension -import org.gradle.api.Task -import org.gradle.internal.classloader.ClassLoaderHierarchyHasher -import org.gradle.internal.classloader.ClassLoaderVisitor -import java.net.URLClassLoader - - -class Visitor( - private val buildScan: BuildScanExtension, - private val hasher: ClassLoaderHierarchyHasher, - private val prefix: String -) : ClassLoaderVisitor() { - - private - var counter = 0 - - constructor(buildScan: BuildScanExtension, hasher: ClassLoaderHierarchyHasher, task: Task) : - this(buildScan, hasher, "${task.path}-classloader") - - private - fun classloaderHash(loader: ClassLoader): String? { - return hasher.getClassLoaderHash(loader)?.toString() - } - - override fun visit(classLoader: ClassLoader) { - val hash = classloaderHash(classLoader) - if (!hash.isNullOrEmpty()) { - val classloaderName = classLoader::class.java.simpleName - buildScan.value("$prefix-${counter++}-$classloaderName-hash", hash) - if ((this.counter <= 2) && (classLoader is URLClassLoader && (!classloaderName.contains("ExtClassLoader")))) { - buildScan.value("$prefix-${counter - 1}-classpath", classLoader.urLs.joinToString(":")) - } - } - super.visit(classLoader) - } -} diff --git a/buildSrc/subprojects/uber-plugins/uber-plugins.gradle.kts b/buildSrc/subprojects/uber-plugins/uber-plugins.gradle.kts index 5f3b98f189571..638966d400e1b 100644 --- a/buildSrc/subprojects/uber-plugins/uber-plugins.gradle.kts +++ b/buildSrc/subprojects/uber-plugins/uber-plugins.gradle.kts @@ -1,5 +1,6 @@ dependencies { implementation(project(":binaryCompatibility")) + implementation(project(":buildquality")) implementation(project(":cleanup")) implementation(project(":configuration")) implementation(project(":kotlinDsl")) diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle index dc3243f684be7..4b6667adfadfc 100755 --- a/gradle/dependencies.gradle +++ b/gradle/dependencies.gradle @@ -40,13 +40,11 @@ libraries.asm_analysis = [coordinates: 'org.ow2.asm:asm-analysis', versio libraries.awsS3_core = [coordinates: 'com.amazonaws:aws-java-sdk-core', version: '1.11.407'] libraries.awsS3_s3 = [coordinates: 'com.amazonaws:aws-java-sdk-s3', version: libraries.awsS3_core.version] libraries.awsS3_kms = [coordinates: 'com.amazonaws:aws-java-sdk-kms', version: libraries.awsS3_core.version] -libraries.bouncycastle_provider = [coordinates: 'org.bouncycastle:bcprov-jdk15on', version: '1.60'] +libraries.bouncycastle_provider = [coordinates: 'org.bouncycastle:bcprov-jdk15on', version: '1.61'] libraries.bouncycastle_pgp = [coordinates: 'org.bouncycastle:bcpg-jdk15on', version: libraries.bouncycastle_provider.version] libraries.bndlib = [coordinates: 'biz.aQute.bnd:biz.aQute.bndlib', version: '4.0.0'] libraries.bsh = [coordinates: 'org.apache-extras.beanshell:bsh', version: '2.0b6'] -libraries.commons_cli = [coordinates: 'commons-cli:commons-cli', version: '1.4'] libraries.commons_codec = [coordinates: 'commons-codec:commons-codec', version: '1.11'] -libraries.commons_collections = [coordinates: 'commons-collections:commons-collections', version: '3.2.2'] libraries.commons_compress = [coordinates: 'org.apache.commons:commons-compress', version: '1.18'] libraries.commons_httpclient = [coordinates: 'org.apache.httpcomponents:httpclient', version: '4.5.6'] libraries.commons_io = [coordinates: 'commons-io:commons-io', version: '2.6'] @@ -67,7 +65,6 @@ libraries.jansi = [coordinates: 'org.fusesource.jansi:jansi', vers libraries.jatl = [coordinates: 'com.googlecode.jatl:jatl', version: '0.2.3'] libraries.jaxb = [coordinates: 'com.sun.xml.bind:jaxb-impl', version: '2.3.1'] libraries.jcifs = [coordinates: 'org.samba.jcifs:jcifs', version: '1.3.17'] -libraries.jcip = [coordinates: 'net.jcip:jcip-annotations', version: '1.0'] libraries.jgit = [coordinates: 'org.eclipse.jgit:org.eclipse.jgit', version: '5.0.3.201809091024-r'] libraries.joda = [coordinates: 'joda-time:joda-time', version: '2.10'] libraries.jsch = [coordinates: 'com.jcraft:jsch', version: '0.1.54'] @@ -95,6 +92,7 @@ libraries.slf4j_api = [coordinates: 'org.slf4j:slf4j-api', version: '1 libraries.jcl_to_slf4j = [coordinates: 'org.slf4j:jcl-over-slf4j', version: libraries.slf4j_api.version] libraries.jul_to_slf4j = [coordinates: 'org.slf4j:jul-to-slf4j', version: libraries.slf4j_api.version] libraries.log4j_to_slf4j = [coordinates: 'org.slf4j:log4j-over-slf4j', version: libraries.slf4j_api.version] +libraries.ansi_control_sequence_util = [coordinates: 'net.rubygrapefruit:ansi-control-sequence-util', version: '0.2'] // these are transitive dependencies that are part of the Gradle distribution libraries.jetbrains_annotations = [coordinates: 'org.jetbrains:annotations', version: '13.0'] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 607a6cf23437c..5c2d1cf016b38 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0d9e4b132016f..5e8b9009c2c87 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-5.3-20190218000054+0000-bin.zip +distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-5.4-20190329080509+0000-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 061dbc9832724..08282de827e79 100755 --- a/gradlew +++ b/gradlew @@ -1,5 +1,21 @@ #!/usr/bin/env sh +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + ############################################################################## ## ## Gradle start up script for UN*X @@ -28,7 +44,7 @@ APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m"' +DEFAULT_JVM_OPTS='-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" diff --git a/gradlew.bat b/gradlew.bat index 2f6e50f4746e6..dbd960e68f2d7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -14,7 +30,7 @@ set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" +set DEFAULT_JVM_OPTS=-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome diff --git a/released-versions.json b/released-versions.json index c81416bf1e813..478dc1670801c 100644 --- a/released-versions.json +++ b/released-versions.json @@ -1,13 +1,21 @@ { "latestReleaseSnapshot": { - "version": "5.3-20190222020858+0000", - "buildTime": "20190222020858+0000" + "version": "5.4-20190403012714+0000", + "buildTime": "20190403012714+0000" }, "latestRc": { - "version": "5.2-rc-1", - "buildTime": "20190128225604+0000" + "version": "5.3-rc-3", + "buildTime": "20190313202708+0000" }, "finalReleases": [ + { + "version": "5.3.1", + "buildTime": "20190328090923+0000" + }, + { + "version": "5.3", + "buildTime": "20190320110329+0000" + }, { "version": "5.2.1", "buildTime": "20190208190010+0000" diff --git a/settings.gradle.kts b/settings.gradle.kts index 5e4f925bb600c..01098df0ec7ff 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -133,7 +133,6 @@ val groovyBuildScriptProjects = listOf( "testing-jvm", "testing-junit-platform", "test-kit", - "soak", "smoke-test", "version-control") diff --git a/subprojects/announce/announce.gradle.kts b/subprojects/announce/announce.gradle.kts index 3e6da8bbb7768..a023de7050654 100644 --- a/subprojects/announce/announce.gradle.kts +++ b/subprojects/announce/announce.gradle.kts @@ -25,7 +25,6 @@ dependencies { gradlebuildJava { moduleType = ModuleType.CORE - } diff --git a/subprojects/base-services/base-services.gradle.kts b/subprojects/base-services/base-services.gradle.kts index 0e41356b8dda3..3e198fbf5bcd5 100644 --- a/subprojects/base-services/base-services.gradle.kts +++ b/subprojects/base-services/base-services.gradle.kts @@ -27,7 +27,6 @@ dependencies { implementation(library("slf4j_api")) implementation(library("commons_lang")) implementation(library("commons_io")) - implementation(library("jcip")) implementation(library("asm")) jmh(library("bouncycastle_provider")) { diff --git a/subprojects/base-services/src/integTest/groovy/org/gradle/internal/SystemPropertiesIntegrationTest.groovy b/subprojects/base-services/src/integTest/groovy/org/gradle/internal/SystemPropertiesIntegrationTest.groovy index 51e00381976d5..aa57b7cb26964 100644 --- a/subprojects/base-services/src/integTest/groovy/org/gradle/internal/SystemPropertiesIntegrationTest.groovy +++ b/subprojects/base-services/src/integTest/groovy/org/gradle/internal/SystemPropertiesIntegrationTest.groovy @@ -28,7 +28,7 @@ class SystemPropertiesIntegrationTest extends ConcurrentSpec { final int threadCount = 100 Factory factory = Mock() String factoryCreationResult = 'test' - File originalJavaHomeDir = SystemProperties.instance.javaHomeDir + File originalJavaHomeDir = new File(System.properties['java.home'] as String) File providedJavaHomeDir = temporaryFolder.file('my/test/java/home/toolprovider') when: @@ -43,10 +43,10 @@ class SystemPropertiesIntegrationTest extends ConcurrentSpec { then: threadCount * factory.create() >> { - assert SystemProperties.instance.javaHomeDir == providedJavaHomeDir + assert new File(System.properties['java.home'] as String) == providedJavaHomeDir factoryCreationResult } - assert SystemProperties.instance.javaHomeDir == originalJavaHomeDir + assert new File(System.properties['java.home'] as String) == originalJavaHomeDir } def "sets a system property for the duration of a Factory operation"() { @@ -88,7 +88,6 @@ class SystemPropertiesIntegrationTest extends ConcurrentSpec { assert System.getProperty(notsetPropertyName) == null } - def "withProperty and withProperties are never run concurrently"() { final int threadCount = 100 def id = UUID.randomUUID().toString() @@ -112,4 +111,28 @@ class SystemPropertiesIntegrationTest extends ConcurrentSpec { then: noExceptionThrown() } + + def "withProperties(Map) and withProperties are never run concurrently"() { + final int threadCount = 100 + def id = UUID.randomUUID().toString() + + when: + async { + threadCount.times { i -> + start { + SystemProperties.instance.withSystemProperties((id): "bar", {"baz"}) + } + start { + SystemProperties.instance.withSystemProperties { + System.properties.each { + assert it.key != id + } + } + } + } + } + + then: + noExceptionThrown() + } } diff --git a/subprojects/base-services/src/integTest/groovy/org/gradle/internal/classloader/ClassLoaderUtilsIntegrationTest.groovy b/subprojects/base-services/src/integTest/groovy/org/gradle/internal/classloader/ClassLoaderUtilsIntegrationTest.groovy deleted file mode 100644 index d3dfd2f70b04a..0000000000000 --- a/subprojects/base-services/src/integTest/groovy/org/gradle/internal/classloader/ClassLoaderUtilsIntegrationTest.groovy +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.classloader - -import com.google.common.io.Files -import org.gradle.integtests.fixtures.AbstractIntegrationSpec -import org.gradle.util.Requires -import org.gradle.util.TestPrecondition -import org.objectweb.asm.ClassWriter -import org.objectweb.asm.MethodVisitor -import org.objectweb.asm.Type -import spock.lang.Unroll - -import static org.objectweb.asm.Opcodes.ACC_PUBLIC -import static org.objectweb.asm.Opcodes.ALOAD -import static org.objectweb.asm.Opcodes.H_INVOKESPECIAL -import static org.objectweb.asm.Opcodes.RETURN -import static org.objectweb.asm.Opcodes.V1_5 - -@Requires(TestPrecondition.JDK9_OR_LATER) -class ClassLoaderUtilsIntegrationTest extends AbstractIntegrationSpec { - private String packageName = ClassLoaderUtils.getPackageName() - - @Unroll - def 'have illegal access warning when trying to inject into #classLoader'() { - given: - buildFile << """ - apply plugin:'java' - - ${jcenterRepository()} - - dependencies { - testCompile gradleApi() - testCompile 'junit:junit:4.12' - } - """ - - file("src/test/java/${packageName.replace('.', '/')}/Test.java") << """ - package ${packageName}; - import java.nio.file.*; - - public class Test { - @org.junit.Test - public void test() throws Exception { - Path path = Paths.get("${file('MyClass.class').absolutePath.replace('\\', '/')}"); - byte[] bytes = Files.readAllBytes(path); - ClassLoaderUtils.define(${classLoader}, "MyClass", bytes); - } - - private static class MyClassLoader extends ClassLoader { } - } - """ - - createClassFile() - - when: - succeeds('test') - - then: - result.hasErrorOutput("Illegal reflective access using Lookup on ${ClassLoaderUtils.name}") == hasWarning - - where: - classLoader | hasWarning - 'ClassLoader.getSystemClassLoader()' | true - 'new MyClassLoader()' | false - } - - void createClassFile() { - ClassWriter cw = new ClassWriter(0) - cw.visit(V1_5, ACC_PUBLIC, "MyClass", null, 'java/lang/Object', null) - MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "", "()V", null, null) - mv.visitMaxs(2, 1) - mv.visitVarInsn(ALOAD, 0) // push `this` to the operand stack - mv.visitMethodInsn(H_INVOKESPECIAL, Type.getInternalName(Object.class), "", "()V", false) - mv.visitInsn(RETURN) - mv.visitEnd() - cw.visitEnd() - - Files.write(cw.toByteArray(), file('MyClass.class')) - } - -} diff --git a/subprojects/base-services/src/main/java/org/gradle/api/file/FileType.java b/subprojects/base-services/src/main/java/org/gradle/api/file/FileType.java new file mode 100644 index 0000000000000..fcabb43c3fea0 --- /dev/null +++ b/subprojects/base-services/src/main/java/org/gradle/api/file/FileType.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.file; + +import org.gradle.api.Incubating; + +/** + * The type of a file. + * + * @since 5.4 + */ +@Incubating +public enum FileType { + FILE, + DIRECTORY, + /** + * Element of an input property pointing to a non-existing file. + */ + MISSING +} diff --git a/subprojects/base-services/src/main/java/org/gradle/api/file/RelativePath.java b/subprojects/base-services/src/main/java/org/gradle/api/file/RelativePath.java index 6a2549dad71f8..7620631f854f9 100644 --- a/subprojects/base-services/src/main/java/org/gradle/api/file/RelativePath.java +++ b/subprojects/base-services/src/main/java/org/gradle/api/file/RelativePath.java @@ -15,8 +15,9 @@ */ package org.gradle.api.file; -import org.gradle.internal.file.FilePathUtil; +import org.gradle.api.file.internal.FilePathUtil; +import javax.annotation.Nullable; import java.io.File; import java.io.Serializable; import java.nio.CharBuffer; @@ -44,7 +45,7 @@ public RelativePath(boolean endsWithFile, String... segments) { this(endsWithFile, null, segments); } - private RelativePath(boolean endsWithFile, RelativePath parentPath, String... childSegments) { + private RelativePath(boolean endsWithFile, @Nullable RelativePath parentPath, String... childSegments) { this.endsWithFile = endsWithFile; int targetOffsetForChildSegments; if (parentPath != null) { @@ -68,9 +69,7 @@ private static void copySegments(String[] target, String[] source, int length) { } private static void copyAndInternSegments(String[] target, int targetOffset, String[] source) { - for (int i = 0; i < source.length; i++) { - target[targetOffset + i] = source[i]; - } + System.arraycopy(source, 0, target, targetOffset, source.length); } public String[] getSegments() { @@ -143,6 +142,7 @@ public File getFile(File baseDir) { return new File(baseDir, getPathString()); } + @Nullable public String getLastName() { if (segments.length > 0) { return segments[segments.length - 1]; @@ -185,6 +185,7 @@ public String toString() { * * @return The parent of this path, or null if this is the root path. */ + @Nullable public RelativePath getParent() { switch (segments.length) { case 0: @@ -202,7 +203,7 @@ public static RelativePath parse(boolean isFile, String path) { return parse(isFile, null, path); } - public static RelativePath parse(boolean isFile, RelativePath parent, String path) { + public static RelativePath parse(boolean isFile, @Nullable RelativePath parent, String path) { String[] names = FilePathUtil.getPathSegments(path); return new RelativePath(isFile, parent, names); } @@ -271,8 +272,8 @@ public int compareTo(RelativePath o) { } int lim = Math.min(len1, len2); - String v1[] = segments; - String v2[] = o.segments; + String[] v1 = segments; + String[] v2 = o.segments; int k = 0; while (k < lim) { diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/file/FilePathUtil.java b/subprojects/base-services/src/main/java/org/gradle/api/file/internal/FilePathUtil.java similarity index 98% rename from subprojects/base-services/src/main/java/org/gradle/internal/file/FilePathUtil.java rename to subprojects/base-services/src/main/java/org/gradle/api/file/internal/FilePathUtil.java index 749a71833c9fe..2147ca1ef2be8 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/file/FilePathUtil.java +++ b/subprojects/base-services/src/main/java/org/gradle/api/file/internal/FilePathUtil.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.gradle.internal.file; +package org.gradle.api.file.internal; import org.apache.commons.lang.StringUtils; diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/GeneratedSubclass.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/GeneratedSubclass.java index 329b38a62370f..f09154d9ecd9f 100644 --- a/subprojects/base-services/src/main/java/org/gradle/api/internal/GeneratedSubclass.java +++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/GeneratedSubclass.java @@ -20,4 +20,5 @@ * Marker interface for types that are runtime generated subclasses by {@link ClassGenerator} */ public interface GeneratedSubclass { + Class publicType(); } diff --git a/subprojects/base-services/src/main/java/org/gradle/api/internal/GeneratedSubclasses.java b/subprojects/base-services/src/main/java/org/gradle/api/internal/GeneratedSubclasses.java index 5db28b0b7e1dd..c24ec47227e1c 100644 --- a/subprojects/base-services/src/main/java/org/gradle/api/internal/GeneratedSubclasses.java +++ b/subprojects/base-services/src/main/java/org/gradle/api/internal/GeneratedSubclasses.java @@ -16,17 +16,28 @@ package org.gradle.api.internal; +import org.gradle.internal.UncheckedException; + public class GeneratedSubclasses { private GeneratedSubclasses() { } - public static Class unpack(Class clazz) { - if (GeneratedSubclass.class.isAssignableFrom(clazz)) { - return unpack(clazz.getSuperclass()); - } else { - return clazz; + public static Class unpack(Class type) { + if (GeneratedSubclass.class.isAssignableFrom(type)) { + try { + return (Class) type.getMethod("generatedFrom").invoke(null); + } catch (Exception e) { + throw UncheckedException.throwAsUncheckedException(e); + } } + return type; } + public static Class unpackType(Object object) { + if (object instanceof GeneratedSubclass) { + return ((GeneratedSubclass) object).publicType(); + } + return object.getClass(); + } } diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java b/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java index 7e962a9654d87..2aa4b7fc6d435 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/Actions.java @@ -91,6 +91,7 @@ public static Action composite(List> actions) * @param The type of the object that action is for * @return The composite action. */ + @SafeVarargs public static Action composite(Action... actions) { List> filtered = Lists.newArrayListWithCapacity(actions.length); for (Action action : actions) { diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java b/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java index a4451f42c3286..d1b8f057c64c9 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/SystemProperties.java @@ -15,9 +15,13 @@ */ package org.gradle.internal; +import com.google.common.collect.Maps; + +import javax.annotation.Nullable; import java.io.File; import java.util.Collections; import java.util.HashSet; +import java.util.Map; import java.util.Set; @@ -71,34 +75,30 @@ public static SystemProperties getInstance() { private SystemProperties() { } - public synchronized String getLineSeparator() { + public String getLineSeparator() { return System.getProperty("line.separator"); } - public synchronized String getJavaIoTmpDir() { + public String getJavaIoTmpDir() { return System.getProperty("java.io.tmpdir"); } - public synchronized String getUserHome() { + public String getUserHome() { return System.getProperty("user.home"); } - public synchronized String getUserName() { + public String getUserName() { return System.getProperty("user.name"); } - public synchronized String getJavaVersion() { + public String getJavaVersion() { return System.getProperty("java.version"); } - public synchronized File getCurrentDir() { + public File getCurrentDir() { return new File(System.getProperty("user.dir")); } - public synchronized File getJavaHomeDir() { - return new File(System.getProperty("java.home")); - } - /** * Creates instance for Factory implementation with the provided Java home directory. Setting the "java.home" system property is thread-safe * and is set back to the original value of "java.home" after the operation. @@ -120,28 +120,73 @@ public T withJavaHome(File javaHomeDir, Factory factory) { * @param factory Instance created by the Factory implementation */ public synchronized T withSystemProperty(String propertyName, String value, Factory factory) { - String originalValue = System.getProperty(propertyName); - System.setProperty(propertyName, value); + String originalValue = overrideProperty(propertyName, value); try { return factory.create(); } finally { - if (originalValue != null) { - System.setProperty(propertyName, originalValue); - } else { - System.clearProperty(propertyName); - } + restoreProperty(propertyName, originalValue); } } /** * Provides safe access to the system properties, preventing concurrent {@link #withSystemProperty(String, String, Factory)} calls. + * * This can be used to wrap 3rd party APIs that iterate over the system properties, so they won't result in {@link java.util.ConcurrentModificationException}s. + * + * This method should not be used when you need to temporarily change system properties. */ public synchronized T withSystemProperties(Factory factory) { return factory.create(); } + /** + * Provides safe access to the system properties, preventing concurrent calls to change system properties. + * + * This can be used to wrap 3rd party APIs that iterate over the system properties, so they won't result in {@link java.util.ConcurrentModificationException}s. + * + * This method can be used to override system properties temporarily. The original values of the given system properties are restored before returning. + */ + public synchronized T withSystemProperties(Map properties, Factory factory) { + Map originalProperties = Maps.newHashMap(); + for (Map.Entry property : properties.entrySet()) { + String propertyName = property.getKey(); + String value = property.getValue(); + String originalValue = overrideProperty(propertyName, value); + originalProperties.put(propertyName, originalValue); + } + + try { + return factory.create(); + } finally { + for (Map.Entry property : originalProperties.entrySet()) { + String propertyName = property.getKey(); + String originalValue = property.getValue(); + restoreProperty(propertyName, originalValue); + } + } + } + + @Nullable + private String overrideProperty(String propertyName, String value) { + // Overwrite property + String originalValue = System.getProperty(propertyName); + if (value != null) { + System.setProperty(propertyName, value); + } else { + System.clearProperty(propertyName); + } + return originalValue; + } + + private void restoreProperty(String propertyName, @Nullable String originalValue) { + if (originalValue != null) { + System.setProperty(propertyName, originalValue); + } else { + System.clearProperty(propertyName); + } + } + /** * Returns true if the given key is guaranteed to be contained in System.getProperties() by default, * as specified in the Javadoc for that method. diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/Try.java b/subprojects/base-services/src/main/java/org/gradle/internal/Try.java index cba4f99c364b6..0b65f2dccdfa0 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/Try.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/Try.java @@ -65,9 +65,11 @@ public Try apply(T input) { public abstract void ifSuccessful(Consumer consumer); - public abstract void ifSuccessfulOrElse(Consumer successConsumer, Consumer failureConsumer); + public abstract void ifSuccessfulOrElse(Consumer successConsumer, Consumer failureConsumer); - private static class Success extends Try { + public abstract R getSuccessfulOrElse(Function successFunction, Function failureFunction); + + private static final class Success extends Try { private final T value; public Success(T value) { @@ -114,10 +116,15 @@ public void ifSuccessful(Consumer consumer) { } @Override - public void ifSuccessfulOrElse(Consumer successConsumer, Consumer failureConsumer) { + public void ifSuccessfulOrElse(Consumer successConsumer, Consumer failureConsumer) { successConsumer.accept(value); } + @Override + public R getSuccessfulOrElse(Function successFunction, Function failureFunction) { + return successFunction.apply(value); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -138,7 +145,7 @@ public int hashCode() { } } - private static class Failure extends Try { + private static final class Failure extends Try { private final Throwable failure; public Failure(Throwable failure) { @@ -180,10 +187,15 @@ public void ifSuccessful(Consumer consumer) { } @Override - public void ifSuccessfulOrElse(Consumer successConsumer, Consumer failureConsumer) { + public void ifSuccessfulOrElse(Consumer successConsumer, Consumer failureConsumer) { failureConsumer.accept(failure); } + @Override + public R getSuccessfulOrElse(Function successFunction, Function failureFunction) { + return failureFunction.apply(failure); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/exceptions/DefaultMultiCauseException.java b/subprojects/base-services/src/main/java/org/gradle/internal/exceptions/DefaultMultiCauseException.java index 45a03676fcb84..bda9388362e7c 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/exceptions/DefaultMultiCauseException.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/exceptions/DefaultMultiCauseException.java @@ -16,6 +16,8 @@ package org.gradle.internal.exceptions; import org.gradle.api.GradleException; +import org.gradle.internal.Factory; + import java.io.IOException; import java.io.ObjectInputStream; import java.io.PrintStream; @@ -27,18 +29,37 @@ public class DefaultMultiCauseException extends GradleException implements MultiCauseException { private final List causes = new CopyOnWriteArrayList(); private transient ThreadLocal hideCause = threadLocal(); + private transient Factory messageFactory; + private String message; public DefaultMultiCauseException(String message) { super(message); + this.message = message; } public DefaultMultiCauseException(String message, Throwable... causes) { super(message); + this.message = message; this.causes.addAll(Arrays.asList(causes)); } public DefaultMultiCauseException(String message, Iterable causes) { super(message); + this.message = message; + initCauses(causes); + } + + public DefaultMultiCauseException(Factory messageFactory) { + this.messageFactory = messageFactory; + } + + public DefaultMultiCauseException(Factory messageFactory, Throwable... causes) { + this(messageFactory); + this.causes.addAll(Arrays.asList(causes)); + } + + public DefaultMultiCauseException(Factory messageFactory, Iterable causes) { + this(messageFactory); initCauses(causes); } @@ -47,6 +68,11 @@ private void readObject(ObjectInputStream inputStream) throws IOException, Class hideCause = threadLocal(); } + private void writeObject(java.io.ObjectOutputStream out) throws IOException { + getMessage(); + out.defaultWriteObject(); + } + private ThreadLocal threadLocal() { return new ThreadLocal() { @Override @@ -108,4 +134,14 @@ public void printStackTrace(PrintWriter printWriter) { hideCause.set(false); } } + + @Override + public String getMessage() { + if (messageFactory != null) { + message = messageFactory.create(); + messageFactory = null; + return message; + } + return message; + } } diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/exceptions/DefaultMultiCauseExceptionNoStackTrace.java b/subprojects/base-services/src/main/java/org/gradle/internal/exceptions/DefaultMultiCauseExceptionNoStackTrace.java new file mode 100644 index 0000000000000..5a741747a22ec --- /dev/null +++ b/subprojects/base-services/src/main/java/org/gradle/internal/exceptions/DefaultMultiCauseExceptionNoStackTrace.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.internal.exceptions; + +import org.gradle.internal.Factory; + +/** + * A specialized version of multi cause exception that is cheaper to create + * because we avoid to fill a stack trace, and the message MUST be generated lazily. + */ +@Contextual +public class DefaultMultiCauseExceptionNoStackTrace extends DefaultMultiCauseException { + public DefaultMultiCauseExceptionNoStackTrace(Factory messageFactory) { + super(messageFactory); + } + + public DefaultMultiCauseExceptionNoStackTrace(Factory messageFactory, Throwable... causes) { + super(messageFactory, causes); + } + + public DefaultMultiCauseExceptionNoStackTrace(Factory messageFactory, Iterable causes) { + super(messageFactory, causes); + } + + @Override + public synchronized Throwable fillInStackTrace() { + return this; + } +} diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/file/DefaultFileHierarchySet.java b/subprojects/base-services/src/main/java/org/gradle/internal/file/DefaultFileHierarchySet.java index 6b41144f0d895..9af55ed63a217 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/file/DefaultFileHierarchySet.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/file/DefaultFileHierarchySet.java @@ -18,6 +18,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; +import org.gradle.api.file.internal.FilePathUtil; import java.io.File; import java.util.ArrayList; diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/file/FileType.java b/subprojects/base-services/src/main/java/org/gradle/internal/file/FileType.java index 29ae238304593..fc01f0b4d0a88 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/file/FileType.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/file/FileType.java @@ -17,7 +17,17 @@ package org.gradle.internal.file; public enum FileType { - RegularFile, - Directory, - Missing + RegularFile(org.gradle.api.file.FileType.FILE), + Directory(org.gradle.api.file.FileType.DIRECTORY), + Missing(org.gradle.api.file.FileType.MISSING); + + private final org.gradle.api.file.FileType publicType; + + FileType(org.gradle.api.file.FileType publicType) { + this.publicType = publicType; + } + + public org.gradle.api.file.FileType toPublicType() { + return publicType; + } } diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/operations/BuildOperationExecutor.java b/subprojects/base-services/src/main/java/org/gradle/internal/operations/BuildOperationExecutor.java index b3894ca910085..2be3733165b32 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/operations/BuildOperationExecutor.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/operations/BuildOperationExecutor.java @@ -16,7 +16,7 @@ package org.gradle.internal.operations; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Action; /** diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java index 8991b9c44b4fc..5794a3d7a78b0 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/service/ServiceRegistry.java @@ -15,9 +15,11 @@ */ package org.gradle.internal.service; +import com.google.common.collect.ImmutableList; import org.gradle.internal.Factory; import org.gradle.internal.scan.UsedByScanPlugin; +import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.List; @@ -88,4 +90,45 @@ public interface ServiceRegistry extends ServiceLookup { * @throws ServiceLookupException On failure to lookup the specified service factory. */ T newInstance(Class type) throws UnknownServiceException, ServiceLookupException; + + ServiceRegistry EMPTY = new ServiceRegistry() { + @Override + public T get(Class serviceType) throws UnknownServiceException, ServiceLookupException { + throw emptyServiceRegistryException(serviceType); + } + + @Override + public List getAll(Class serviceType) throws ServiceLookupException { + return ImmutableList.of(); + } + + @Override + public Object get(Type serviceType) throws UnknownServiceException, ServiceLookupException { + throw emptyServiceRegistryException(serviceType); + } + + @Override + public Object find(Type serviceType) throws ServiceLookupException { + return null; + } + + @Override + public Factory getFactory(Class type) throws UnknownServiceException, ServiceLookupException { + throw emptyServiceRegistryException(type); + } + + private UnknownServiceException emptyServiceRegistryException(Type type) { + return new UnknownServiceException(type, "Nothing is available in the empty service registry."); + } + + @Override + public T newInstance(Class type) throws UnknownServiceException, ServiceLookupException { + throw emptyServiceRegistryException(type); + } + + @Override + public Object get(Type serviceType, Class annotatedWith) throws UnknownServiceException, ServiceLookupException { + throw emptyServiceRegistryException(serviceType); + } + }; } diff --git a/subprojects/base-services/src/main/java/org/gradle/internal/time/Clock.java b/subprojects/base-services/src/main/java/org/gradle/internal/time/Clock.java index d043e241d4dcf..ae9d1f5ae8fad 100644 --- a/subprojects/base-services/src/main/java/org/gradle/internal/time/Clock.java +++ b/subprojects/base-services/src/main/java/org/gradle/internal/time/Clock.java @@ -23,7 +23,7 @@ public interface Clock { /** - * The current time. + * The current time in millis. */ long getCurrentTime(); diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/file/FilePathUtilTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/api/file/internal/FilePathUtilTest.groovy similarity index 93% rename from subprojects/base-services/src/test/groovy/org/gradle/internal/file/FilePathUtilTest.groovy rename to subprojects/base-services/src/test/groovy/org/gradle/api/file/internal/FilePathUtilTest.groovy index 5033ba8c699df..057d75c63cc21 100644 --- a/subprojects/base-services/src/test/groovy/org/gradle/internal/file/FilePathUtilTest.groovy +++ b/subprojects/base-services/src/test/groovy/org/gradle/api/file/internal/FilePathUtilTest.groovy @@ -14,12 +14,12 @@ * limitations under the License. */ -package org.gradle.internal.file +package org.gradle.api.file.internal import spock.lang.Specification import spock.lang.Unroll -import static org.gradle.internal.file.FilePathUtil.sizeOfCommonPrefix +import static org.gradle.api.file.internal.FilePathUtil.sizeOfCommonPrefix class FilePathUtilTest extends Specification { diff --git a/subprojects/base-services/src/test/groovy/org/gradle/internal/SystemPropertiesTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/internal/SystemPropertiesTest.groovy index 3b9d52fdd8d99..5aeafffa95b3c 100644 --- a/subprojects/base-services/src/test/groovy/org/gradle/internal/SystemPropertiesTest.groovy +++ b/subprojects/base-services/src/test/groovy/org/gradle/internal/SystemPropertiesTest.groovy @@ -18,60 +18,10 @@ package org.gradle.internal import spock.lang.Specification -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CountDownLatch - class SystemPropertiesTest extends Specification { def "can be queried for standard system properties"() { expect: SystemProperties.instance.isStandardProperty("os.name") !SystemProperties.instance.isStandardProperty("foo.bar") } - - def "prohibits concurrent reads of Java Home while factory is busy"() { - def repeat = 100 - def initialJavaHomePath = SystemProperties.instance.javaHomeDir.path - def temporaryJavaHomePath = initialJavaHomePath + "-2" - - def valuesSeenByFactory = Collections.newSetFromMap(new ConcurrentHashMap()) - - def factory = new Factory() { - @Override - Object create() { - valuesSeenByFactory.add(SystemProperties.instance.javaHomeDir.path) - return new Object() - } - } - - def latch = new CountDownLatch(2) - - new Thread(new Runnable() { - @Override - void run() { - for (int i = 0; i < repeat; i++) { - SystemProperties.instance.withJavaHome(new File(temporaryJavaHomePath), factory) - } - latch.countDown() - } - }).start() - - def valuesSeenByAnotherThread = Collections.newSetFromMap(new ConcurrentHashMap()) - - new Thread(new Runnable() { - @Override - void run() { - for (int i = 0; i < repeat; i++) { - valuesSeenByAnotherThread.add(SystemProperties.instance.javaHomeDir.path) - } - - latch.countDown() - } - }).start() - - latch.await() - - expect: - valuesSeenByFactory == Collections.singleton(temporaryJavaHomePath) - valuesSeenByAnotherThread == Collections.singleton(initialJavaHomePath) - } } diff --git a/subprojects/base-services/src/test/groovy/org/gradle/util/SetSystemPropertiesTest.groovy b/subprojects/base-services/src/test/groovy/org/gradle/util/SetSystemPropertiesTest.groovy new file mode 100644 index 0000000000000..1e1a102d0327b --- /dev/null +++ b/subprojects/base-services/src/test/groovy/org/gradle/util/SetSystemPropertiesTest.groovy @@ -0,0 +1,56 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.util + +import org.junit.runner.Description +import org.junit.runners.model.Statement +import spock.lang.Specification + +class SetSystemPropertiesTest extends Specification { + public static final String TEST_PROPERTY = 'org.gradle.foo' + def base = Mock(Statement) + + def "can set system properties for the duration of a test"() { + def properties = [:] + + given: + System.setProperty(TEST_PROPERTY, "bar") + properties[TEST_PROPERTY] = "baz" + SetSystemProperties setSystemProperties = new SetSystemProperties(properties) + + when: + setSystemProperties.apply(base, Stub(Description)).evaluate() + + then: + 1 * base.evaluate() >> { assert System.getProperty(TEST_PROPERTY) == "baz" } + + and: + System.getProperty(TEST_PROPERTY) == "bar" + } + + def "cannot set java.io.tmpdir"() { + given: + SetSystemProperties setSystemProperties = new SetSystemProperties(["java.io.tmpdir": "/some/path"]) + + when: + setSystemProperties.apply(base, Stub(Description)).evaluate() + + then: + def e = thrown(IllegalArgumentException) + e.message == "'java.io.tmpdir' should not be set via a rule as its value cannot be changed once it is initialized" + } +} diff --git a/subprojects/build-cache-http/src/integTest/groovy/org/gradle/caching/http/internal/HttpBuildCacheServiceErrorHandlingIntegrationTest.groovy b/subprojects/build-cache-http/src/integTest/groovy/org/gradle/caching/http/internal/HttpBuildCacheServiceErrorHandlingIntegrationTest.groovy index 3486715619734..ef0fca8bd9a2b 100644 --- a/subprojects/build-cache-http/src/integTest/groovy/org/gradle/caching/http/internal/HttpBuildCacheServiceErrorHandlingIntegrationTest.groovy +++ b/subprojects/build-cache-http/src/integTest/groovy/org/gradle/caching/http/internal/HttpBuildCacheServiceErrorHandlingIntegrationTest.groovy @@ -64,7 +64,7 @@ class HttpBuildCacheServiceErrorHandlingIntegrationTest extends AbstractIntegrat withBuildCache().run "customTask" then: - output =~ /Could not store entry .* for task ':customTask' in remote build cache: ${errorPattern}/ + output =~ /Could not store entry .* in remote build cache: ${errorPattern}/ } def "build cache is deactivated for the build if the connection times out"() { @@ -77,7 +77,7 @@ class HttpBuildCacheServiceErrorHandlingIntegrationTest extends AbstractIntegrat withBuildCache().run("customTask") then: - output =~ /Could not load entry .* for task ':customTask' from remote build cache: Read timed out/ + output =~ /Could not load entry .* from remote build cache: Read timed out/ } private void startServer() { diff --git a/subprojects/build-cache-http/src/integTest/groovy/org/gradle/caching/http/internal/HttpBuildCacheServiceTest.groovy b/subprojects/build-cache-http/src/integTest/groovy/org/gradle/caching/http/internal/HttpBuildCacheServiceTest.groovy index 6d6765f6882cd..3058856394211 100644 --- a/subprojects/build-cache-http/src/integTest/groovy/org/gradle/caching/http/internal/HttpBuildCacheServiceTest.groovy +++ b/subprojects/build-cache-http/src/integTest/groovy/org/gradle/caching/http/internal/HttpBuildCacheServiceTest.groovy @@ -25,6 +25,7 @@ import org.gradle.caching.BuildCacheKey import org.gradle.caching.BuildCacheService import org.gradle.caching.BuildCacheServiceFactory import org.gradle.caching.http.HttpBuildCache +import org.gradle.internal.hash.HashCode import org.gradle.internal.resource.transport.http.DefaultSslContextFactory import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider import org.gradle.test.fixtures.server.http.AuthScheme @@ -58,9 +59,11 @@ class HttpBuildCacheServiceTest extends Specification { BuildCacheServiceFactory.Describer buildCacheDescriber def key = new BuildCacheKey() { + def hashCode = HashCode.fromString("01234567abcdef") + @Override String getHashCode() { - return '0123456abcdef' + return hashCode.toString() } @Override @@ -68,6 +71,11 @@ class HttpBuildCacheServiceTest extends Specification { return getHashCode() } + @Override + byte[] toByteArray() { + return hashCode.toByteArray() + } + @Override String getDisplayName() { return getHashCode() diff --git a/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/command/BuildCacheCommandFactory.java b/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/command/BuildCacheCommandFactory.java index 34dd3284b6c03..bb353f7f10836 100644 --- a/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/command/BuildCacheCommandFactory.java +++ b/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/command/BuildCacheCommandFactory.java @@ -17,12 +17,7 @@ package org.gradle.caching.internal.command; import com.google.common.collect.ImmutableSortedMap; -import org.apache.commons.io.FileUtils; -import org.gradle.api.GradleException; -import org.gradle.api.UncheckedIOException; import org.gradle.api.internal.cache.StringInterner; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.caching.BuildCacheKey; import org.gradle.caching.internal.CacheableEntity; import org.gradle.caching.internal.controller.BuildCacheLoadCommand; @@ -30,7 +25,6 @@ import org.gradle.caching.internal.origin.OriginMetadata; import org.gradle.caching.internal.origin.OriginMetadataFactory; import org.gradle.caching.internal.packaging.BuildCacheEntryPacker; -import org.gradle.caching.internal.packaging.UnrecoverableUnpackingException; import org.gradle.internal.file.FileType; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; import org.gradle.internal.fingerprint.FingerprintingStrategy; @@ -41,8 +35,9 @@ import org.gradle.internal.snapshot.FileSystemMirror; import org.gradle.internal.snapshot.FileSystemSnapshot; import org.gradle.internal.snapshot.MissingFileSnapshot; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -52,7 +47,7 @@ public class BuildCacheCommandFactory { - private static final Logger LOGGER = Logging.getLogger(BuildCacheCommandFactory.class); + private static final Logger LOGGER = LoggerFactory.getLogger(BuildCacheCommandFactory.class); private final BuildCacheEntryPacker packer; private final OriginMetadataFactory originMetadataFactory; @@ -66,8 +61,8 @@ public BuildCacheCommandFactory(BuildCacheEntryPacker packer, OriginMetadataFact this.stringInterner = stringInterner; } - public BuildCacheLoadCommand createLoad(BuildCacheKey cacheKey, CacheableEntity entity, BuildCacheLoadListener loadListener) { - return new LoadCommand(cacheKey, entity, loadListener); + public BuildCacheLoadCommand createLoad(BuildCacheKey cacheKey, CacheableEntity entity) { + return new LoadCommand(cacheKey, entity); } public BuildCacheStoreCommand createStore(BuildCacheKey cacheKey, CacheableEntity entity, Map fingerprints, long executionTime) { @@ -83,12 +78,10 @@ private class LoadCommand implements BuildCacheLoadCommand { private final BuildCacheKey cacheKey; private final CacheableEntity entity; - private final BuildCacheLoadListener loadListener; - private LoadCommand(BuildCacheKey cacheKey, CacheableEntity entity, BuildCacheLoadListener loadListener) { + private LoadCommand(BuildCacheKey cacheKey, CacheableEntity entity) { this.cacheKey = cacheKey; this.entity = entity; - this.loadListener = loadListener; } @Override @@ -97,46 +90,31 @@ public BuildCacheKey getKey() { } @Override - public BuildCacheLoadCommand.Result load(InputStream input) { - loadListener.beforeLoad(); - try { - BuildCacheEntryPacker.UnpackResult unpackResult = packer.unpack(entity, input, originMetadataFactory.createReader(entity)); - ImmutableSortedMap snapshots = snapshotUnpackedData(unpackResult.getSnapshots()); - LOGGER.info("Unpacked trees for {} from cache.", entity.getDisplayName()); - return new Result() { - @Override - public long getArtifactEntryCount() { - return unpackResult.getEntries(); - } - - @Override - public LoadMetadata getMetadata() { - return new LoadMetadata() { - @Override - public OriginMetadata getOriginMetadata() { - return unpackResult.getOriginMetadata(); - } - - @Override - public ImmutableSortedMap getResultingSnapshots() { - return snapshots; - } - }; - } - }; - } catch (Exception e) { - LOGGER.warn("Cleaning {} after failed load from cache.", entity.getDisplayName()); - try { - cleanupTreesAfterUnpackFailure(); - loadListener.afterLoadFailedAndWasCleanedUp(e); - } catch (Exception eCleanup) { - LOGGER.warn("Unrecoverable error during cleaning up after unpack failure", eCleanup); - throw new UnrecoverableUnpackingException(String.format("Failed to unpack trees for %s, and then failed to clean up; see log above for details", entity.getDisplayName()), e); + public BuildCacheLoadCommand.Result load(InputStream input) throws IOException { + BuildCacheEntryPacker.UnpackResult unpackResult = packer.unpack(entity, input, originMetadataFactory.createReader(entity)); + ImmutableSortedMap snapshots = snapshotUnpackedData(unpackResult.getSnapshots()); + LOGGER.info("Unpacked trees for {} from cache.", entity.getDisplayName()); + return new Result() { + @Override + public long getArtifactEntryCount() { + return unpackResult.getEntries(); + } + + @Override + public LoadMetadata getMetadata() { + return new LoadMetadata() { + @Override + public OriginMetadata getOriginMetadata() { + return unpackResult.getOriginMetadata(); + } + + @Override + public ImmutableSortedMap getResultingSnapshots() { + return snapshots; + } + }; } - throw new GradleException(String.format("Failed to unpack trees for %s", entity.getDisplayName()), e); - } finally { - cleanLocalState(); - } + }; } private ImmutableSortedMap snapshotUnpackedData(Map treeSnapshots) { @@ -174,36 +152,6 @@ private ImmutableSortedMap snapshotUnp }); return builder.build(); } - - private void cleanLocalState() { - entity.visitLocalState(localStateFile -> { - try { - remove(localStateFile); - } catch (IOException ex) { - throw new UncheckedIOException(String.format("Failed to clean up local state files for %s: %s", entity.getDisplayName(), localStateFile), ex); - } - }); - } - - private void cleanupTreesAfterUnpackFailure() { - entity.visitOutputTrees((name, type, root) -> { - try { - remove(root); - } catch (IOException ex) { - throw new UncheckedIOException(String.format("Failed to clean up files for tree '%s' of %s: %s", name, entity.getDisplayName(), root), ex); - } - }); - } - - private void remove(File file) throws IOException { - if (file.exists()) { - if (file.isDirectory()) { - FileUtils.cleanDirectory(file); - } else { - FileUtils.forceDelete(file); - } - } - } } private class StoreCommand implements BuildCacheStoreCommand { diff --git a/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/packaging/impl/RelativePathParser.java b/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/packaging/impl/RelativePathParser.java index 167a434b66336..7025a93a802ee 100644 --- a/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/packaging/impl/RelativePathParser.java +++ b/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/packaging/impl/RelativePathParser.java @@ -17,7 +17,7 @@ package org.gradle.caching.internal.packaging.impl; import com.google.common.base.CharMatcher; -import org.gradle.internal.file.FilePathUtil; +import org.gradle.api.file.internal.FilePathUtil; import java.util.Deque; import java.util.LinkedList; diff --git a/subprojects/build-cache-packaging/src/test/groovy/org/gradle/caching/internal/command/BuildCacheCommandFactoryTest.groovy b/subprojects/build-cache-packaging/src/test/groovy/org/gradle/caching/internal/command/BuildCacheCommandFactoryTest.groovy index 155eeffe51367..77da94db93893 100644 --- a/subprojects/build-cache-packaging/src/test/groovy/org/gradle/caching/internal/command/BuildCacheCommandFactoryTest.groovy +++ b/subprojects/build-cache-packaging/src/test/groovy/org/gradle/caching/internal/command/BuildCacheCommandFactoryTest.groovy @@ -27,14 +27,12 @@ import org.gradle.caching.internal.origin.OriginMetadataFactory import org.gradle.caching.internal.origin.OriginReader import org.gradle.caching.internal.origin.OriginWriter import org.gradle.caching.internal.packaging.BuildCacheEntryPacker -import org.gradle.caching.internal.packaging.UnrecoverableUnpackingException import org.gradle.internal.file.TreeType import org.gradle.internal.hash.HashCode import org.gradle.internal.nativeintegration.filesystem.DefaultFileMetadata import org.gradle.internal.snapshot.DirectorySnapshot import org.gradle.internal.snapshot.FileSystemMirror import org.gradle.internal.snapshot.RegularFileSnapshot -import org.gradle.internal.time.Timer import org.gradle.test.fixtures.file.CleanupTestDirectory import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider import org.gradle.testing.internal.util.Specification @@ -52,8 +50,6 @@ class BuildCacheCommandFactoryTest extends Specification { def commandFactory = new BuildCacheCommandFactory(packer, originFactory, fileSystemMirror, stringInterner) def key = Mock(BuildCacheKey) - def loadListener = Mock(BuildCacheLoadListener) - def timer = Stub(Timer) def originMetadata = Mock(OriginMetadata) def originReader = Mock(OriginReader) @@ -61,8 +57,6 @@ class BuildCacheCommandFactoryTest extends Specification { @Rule TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider() - def localStateFile = temporaryFolder.file("local-state.txt").createFile() - def "load invokes unpacker and fingerprints trees"() { def outputFile = temporaryFolder.file("output.txt") def outputDir = temporaryFolder.file("outputDir") @@ -72,7 +66,7 @@ class BuildCacheCommandFactoryTest extends Specification { prop("outputDir", DIRECTORY, outputDir), prop("outputFile", FILE, outputFile) ) - def load = commandFactory.createLoad(key, entity, loadListener) + def load = commandFactory.createLoad(key, entity) def outputFileSnapshot = new RegularFileSnapshot(outputFile.absolutePath, outputFile.name, HashCode.fromInt(234), 234) def fileSnapshots = ImmutableMap.of( @@ -83,7 +77,6 @@ class BuildCacheCommandFactoryTest extends Specification { def result = load.load(input) then: - 1 * loadListener.beforeLoad() 1 * originFactory.createReader(entity) >> originReader then: @@ -110,22 +103,18 @@ class BuildCacheCommandFactoryTest extends Specification { result.metadata.resultingSnapshots["outputFile"].fingerprints.keySet() == [outputFile.absolutePath] as Set result.metadata.resultingSnapshots["outputDir"].fingerprints.keySet() == [outputDir, outputDirFile]*.absolutePath as Set 0 * _ - - then: - !localStateFile.exists() } - def "after failed unpacking output is cleaned up"() { + def "after failed unpacking error is propagated and output is not removed"() { def input = Mock(InputStream) def outputFile = temporaryFolder.file("output.txt") def entity = this.entity(prop("output", FILE, outputFile)) - def command = commandFactory.createLoad(key, entity, loadListener) + def command = commandFactory.createLoad(key, entity) when: command.load(input) then: - 1 * loadListener.beforeLoad() 1 * originFactory.createReader(entity) >> originReader then: @@ -134,47 +123,11 @@ class BuildCacheCommandFactoryTest extends Specification { throw new RuntimeException("unpacking error") } - then: - 1 * loadListener.afterLoadFailedAndWasCleanedUp(_ as Throwable) - then: def ex = thrown Exception - !(ex instanceof UnrecoverableUnpackingException) - ex.cause.message == "unpacking error" - !outputFile.exists() + ex.message == "unpacking error" + outputFile.exists() 0 * _ - - then: - !localStateFile.exists() - } - - def "error during cleanup of failed unpacking is reported"() { - def input = Mock(InputStream) - def entity = entity() - def command = commandFactory.createLoad(key, entity, loadListener) - - when: - command.load(input) - - then: - 1 * loadListener.beforeLoad() - 1 * originFactory.createReader(entity) >> originReader - - then: - 1 * packer.unpack(entity, input, originReader) >> { - throw new RuntimeException("unpacking error") - } - - then: - entity.visitOutputTrees(_) >> { throw new RuntimeException("cleanup error") } - - then: - def ex = thrown UnrecoverableUnpackingException - ex.cause.message == "unpacking error" - 0 * _ - - then: - !localStateFile.exists() } def "store invokes packer"() { @@ -202,9 +155,6 @@ class BuildCacheCommandFactoryTest extends Specification { visitOutputTrees(_) >> { CacheableEntity.CacheableTreeVisitor visitor -> trees.each { visitor.visitOutputTree(it.name, it.type, it.root) } } - visitLocalState(_) >> { CacheableEntity.LocalStateVisitor visitor -> - visitor.visitLocalStateRoot(localStateFile) - } } } diff --git a/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/configuration/BuildCacheConfigurationIntegrationTest.groovy b/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/configuration/BuildCacheConfigurationIntegrationTest.groovy index daccf3be15998..7a3204b110227 100644 --- a/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/configuration/BuildCacheConfigurationIntegrationTest.groovy +++ b/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/configuration/BuildCacheConfigurationIntegrationTest.groovy @@ -150,11 +150,17 @@ class BuildCacheConfigurationIntegrationTest extends AbstractIntegrationSpec { assert buildCache.$cache instanceof AnotherBuildCache """ + if (expectDeprecation) { + executer.expectDeprecationWarning() + } + expect: succeeds("help") where: - cache << ["local", "remote"] + cache | expectDeprecation + "local" | true + "remote" | false } def "disables remote cache with --offline"() { @@ -194,7 +200,7 @@ class BuildCacheConfigurationIntegrationTest extends AbstractIntegrationSpec { failureHasCause("Build cache type 'CustomBuildCache' has not been registered.") } - def "emits a useful incubating message when using the build cache"() { + def "emits a useful message when using the build cache"() { when: executer.withBuildCacheEnabled() succeeds("tasks", "--info") @@ -269,6 +275,37 @@ class BuildCacheConfigurationIntegrationTest extends AbstractIntegrationSpec { localBuildCache.empty } + @Unroll + def "shows deprecation warning when using custom local cache using #customCacheConfig"() { + settingsFile << """ + class CustomBuildCache extends AbstractBuildCache {} + class CustomBuildCacheFactory implements BuildCacheServiceFactory { + @Override BuildCacheService createBuildCacheService(CustomBuildCache configuration, Describer describer) { + throw new UnsupportedOperationException() + } + } + + buildCache { + registerBuildCacheService(CustomBuildCache, CustomBuildCacheFactory) + + $customCacheConfig + } + """ + executer.expectDeprecationWarning() + + when: + run "help" + + then: + output.contains("Using a local build cache type other than DirectoryBuildCache has been deprecated. This is scheduled to be removed in Gradle 6.0.") + + where: + customCacheConfig << [ + "local(CustomBuildCache).enabled = false", + "local(CustomBuildCache) { enabled = false }" + ] + } + private static String customTaskCode() { """ @CacheableTask diff --git a/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/internal/BuildCacheBuildOperationsIntegrationTest.groovy b/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/internal/BuildCacheBuildOperationsIntegrationTest.groovy index 16f7e1a256bf3..2b8dc571c84ae 100644 --- a/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/internal/BuildCacheBuildOperationsIntegrationTest.groovy +++ b/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/internal/BuildCacheBuildOperationsIntegrationTest.groovy @@ -227,24 +227,30 @@ class BuildCacheBuildOperationsIntegrationTest extends AbstractIntegrationSpec { } def "records unpack failure"() { - when: - local("reader.execute(new File('not.there'))", "writer.writeTo(new ${NullOutputStream.name}())") - settingsFile << """ - buildCache { local($localCacheClass) } - """ + def localCache = new TestBuildCache(file("local-cache")) + settingsFile << localCache.localCacheConfiguration() + buildFile << cacheableTask() << """ apply plugin: "base" tasks.create("t", CustomTask).paths << "out1" << "out2" """ + run("t") + + // Corrupt cached artifact + localCache.listCacheFiles().each { + it.bytes = [1, 2, 3, 4] + } + + when: executer.withStackTraceChecksDisabled() - succeeds("t") + succeeds("clean", "t") then: def failedUnpackOp = operations.only(BuildCacheArchiveUnpackBuildOperationType) failedUnpackOp.details.cacheKey != null failedUnpackOp.result == null - failedUnpackOp.failure =~ /org.gradle.api.UncheckedIOException:.* not.there/ + failedUnpackOp.failure =~ /java.util.zip.ZipException: Not in GZIP format/ } def "records ops for miss then store"() { @@ -265,6 +271,10 @@ class BuildCacheBuildOperationsIntegrationTest extends AbstractIntegrationSpec { tasks.create("t", CustomTask).paths << "out1" << "out2" """ + if (expectDeprecation) { + executer.expectDeprecationWarning() + } + when: succeeds("t") @@ -287,15 +297,11 @@ class BuildCacheBuildOperationsIntegrationTest extends AbstractIntegrationSpec { operations.orderedSerialSiblings(remoteMissLoadOp, packOp, remoteStoreOp) where: - config << [ - "remote($remoteCacheClass) { push = true }", - "local.push = false; remote($remoteCacheClass) { push = true }", - "local.enabled = false; remote($remoteCacheClass) { push = true }", - "local($remoteCacheClass) { push = true }; remote($remoteCacheClass) { push = true }; " - ] - localStore << [ - true, false, false, false - ] + localStore | expectDeprecation | config + true | false | "remote($remoteCacheClass) { push = true }" + false | false | "local.push = false; remote($remoteCacheClass) { push = true }" + false | false | "local.enabled = false; remote($remoteCacheClass) { push = true }" + false | true | "local($remoteCacheClass) { push = true }; remote($remoteCacheClass) { push = true }; " } def "records ops for remote hit"() { @@ -374,6 +380,8 @@ class BuildCacheBuildOperationsIntegrationTest extends AbstractIntegrationSpec { tasks.create("t", CustomTask).paths << "out1" << "out2" """ + executer.expectDeprecationWarning() + when: succeeds("t") diff --git a/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/internal/FinalizeBuildCacheConfigurationBuildOperationIntegrationTest.groovy b/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/internal/FinalizeBuildCacheConfigurationBuildOperationIntegrationTest.groovy index b99becef08e9a..a4483ca881caf 100644 --- a/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/internal/FinalizeBuildCacheConfigurationBuildOperationIntegrationTest.groovy +++ b/subprojects/build-cache/src/integTest/groovy/org/gradle/caching/internal/FinalizeBuildCacheConfigurationBuildOperationIntegrationTest.groovy @@ -81,6 +81,7 @@ class FinalizeBuildCacheConfigurationBuildOperationIntegrationTest extends Abstr } """ executer.withBuildCacheEnabled() + executer.expectDeprecationWarning() when: succeeds("help") diff --git a/subprojects/build-cache/src/main/java/org/gradle/api/internal/tasks/OriginTaskExecutionMetadata.java b/subprojects/build-cache/src/main/java/org/gradle/api/internal/tasks/OriginTaskExecutionMetadata.java deleted file mode 100644 index c5e54d3c1423b..0000000000000 --- a/subprojects/build-cache/src/main/java/org/gradle/api/internal/tasks/OriginTaskExecutionMetadata.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks; - -import org.gradle.caching.internal.origin.OriginMetadata; -import org.gradle.internal.id.UniqueId; - -/** - * Old location of {@link OriginMetadata}. - * - * This type is used for Kotlin DSL caching. - * Let's keep it for now until the usage in the Kotlin DSL has been removed. - */ -@Deprecated -public class OriginTaskExecutionMetadata extends OriginMetadata { - public OriginTaskExecutionMetadata(UniqueId buildInvocationId, long executionTime) { - super(buildInvocationId, executionTime); - } -} diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/configuration/internal/DefaultBuildCacheConfiguration.java b/subprojects/build-cache/src/main/java/org/gradle/caching/configuration/internal/DefaultBuildCacheConfiguration.java index 4cf1e683172b8..74193cd5e23ce 100644 --- a/subprojects/build-cache/src/main/java/org/gradle/caching/configuration/internal/DefaultBuildCacheConfiguration.java +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/configuration/internal/DefaultBuildCacheConfiguration.java @@ -26,6 +26,7 @@ import org.gradle.internal.Actions; import org.gradle.internal.Cast; import org.gradle.internal.reflect.Instantiator; +import org.gradle.util.DeprecationLogger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -62,13 +63,16 @@ public T local(Class type) { @Override public T local(Class type, Action configuration) { + if (!type.equals(DirectoryBuildCache.class)) { + DeprecationLogger.nagUserOfDeprecated("Using a local build cache type other than " + DirectoryBuildCache.class.getSimpleName()); + } if (!type.isInstance(local)) { if (local != null) { LOGGER.info("Replacing local build cache type {} with {}", local.getClass().getCanonicalName(), type.getCanonicalName()); } local = createLocalCacheConfiguration(instantiator, type, registrations); } - T configurationObject = Cast.uncheckedCast(local); + T configurationObject = Cast.uncheckedNonnullCast(local); configuration.execute(configurationObject); return configurationObject; } @@ -96,7 +100,7 @@ public T remote(Class type, Action configur } remote = createRemoteCacheConfiguration(instantiator, type, registrations); } - T configurationObject = Cast.uncheckedCast(remote); + T configurationObject = Cast.uncheckedNonnullCast(remote); configuration.execute(configurationObject); return configurationObject; } @@ -147,7 +151,7 @@ private static Class> buildCacheServiceFactoryType = registration.getFactoryType(); LOGGER.debug("Found {} registered for {}", buildCacheServiceFactoryType, registeredConfigurationType); - return Cast.uncheckedCast(buildCacheServiceFactoryType); + return Cast.uncheckedNonnullCast(buildCacheServiceFactoryType); } } // Couldn't find a registration for the given type diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/configuration/internal/package-info.java b/subprojects/build-cache/src/main/java/org/gradle/caching/configuration/internal/package-info.java new file mode 100644 index 0000000000000..4021cbf1807cc --- /dev/null +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/configuration/internal/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NonNullApi +package org.gradle.caching.configuration.internal; + +import org.gradle.api.NonNullApi; diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/CacheableEntity.java b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/CacheableEntity.java index 8ee258528f4b8..bf41a5bf23a46 100644 --- a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/CacheableEntity.java +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/CacheableEntity.java @@ -29,15 +29,8 @@ public interface CacheableEntity extends Describable { void visitOutputTrees(CacheableTreeVisitor visitor); - void visitLocalState(LocalStateVisitor visitor); - @FunctionalInterface interface CacheableTreeVisitor { void visitOutputTree(String name, TreeType type, File root); } - - @FunctionalInterface - interface LocalStateVisitor { - void visitLocalStateRoot(File localStateRoot); - } } diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/BuildCacheController.java b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/BuildCacheController.java index afa03af240309..2ae5a7a282091 100644 --- a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/BuildCacheController.java +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/BuildCacheController.java @@ -18,8 +18,8 @@ import org.gradle.caching.BuildCacheService; -import javax.annotation.Nullable; import java.io.Closeable; +import java.util.Optional; /** * Internal coordinator of build cache operations. @@ -32,8 +32,7 @@ public interface BuildCacheController extends Closeable { boolean isEmitDebugLogging(); - @Nullable - T load(BuildCacheLoadCommand command); + Optional load(BuildCacheLoadCommand command); void store(BuildCacheStoreCommand command); diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/BuildCacheControllerFactory.java b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/BuildCacheControllerFactory.java index 2a8a111e54b23..37a2bc47ba1bd 100644 --- a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/BuildCacheControllerFactory.java +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/BuildCacheControllerFactory.java @@ -204,7 +204,7 @@ private static final class BuildCacheDescription implements FinalizeBuildCacheCo private final ImmutableSortedMap config; private BuildCacheDescription(BuildCache buildCache, String type, ImmutableSortedMap config) { - this.className = GeneratedSubclasses.unpack(buildCache.getClass()).getName(); + this.className = GeneratedSubclasses.unpackType(buildCache).getName(); this.push = buildCache.isPush(); this.type = type; this.config = config; diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/DefaultBuildCacheController.java b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/DefaultBuildCacheController.java index e196e43983a8c..f7e42076a97a1 100644 --- a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/DefaultBuildCacheController.java +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/DefaultBuildCacheController.java @@ -47,12 +47,12 @@ import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.RunnableBuildOperation; -import javax.annotation.Nullable; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Optional; public class DefaultBuildCacheController implements BuildCacheController { @@ -105,9 +105,8 @@ public boolean isEmitDebugLogging() { return emitDebugLogging; } - @Nullable @Override - public T load(final BuildCacheLoadCommand command) { + public Optional load(final BuildCacheLoadCommand command) { final Unpack unpack = new Unpack(command); if (local.canLoad()) { @@ -118,7 +117,7 @@ public T load(final BuildCacheLoadCommand command) { } if (unpack.result != null) { - return unpack.result.getMetadata(); + return Optional.of(unpack.result.getMetadata()); } } @@ -155,9 +154,9 @@ public void execute(File file) { BuildCacheLoadCommand.Result result = unpack.result; if (result == null) { - return null; + return Optional.empty(); } else { - return result.getMetadata(); + return Optional.of(result.getMetadata()); } } diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/NoOpBuildCacheController.java b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/NoOpBuildCacheController.java index 594118b31650f..c36fedff215ca 100644 --- a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/NoOpBuildCacheController.java +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/NoOpBuildCacheController.java @@ -16,6 +16,8 @@ package org.gradle.caching.internal.controller; +import java.util.Optional; + public class NoOpBuildCacheController implements BuildCacheController { public static final BuildCacheController INSTANCE = new NoOpBuildCacheController(); @@ -34,8 +36,8 @@ public boolean isEmitDebugLogging() { } @Override - public T load(BuildCacheLoadCommand command) { - return null; + public Optional load(BuildCacheLoadCommand command) { + return Optional.empty(); } @Override diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/RootBuildCacheControllerRef.java b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/RootBuildCacheControllerRef.java index b07ffd8045002..1b83dd9c28e40 100644 --- a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/RootBuildCacheControllerRef.java +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/RootBuildCacheControllerRef.java @@ -16,7 +16,7 @@ package org.gradle.caching.internal.controller; -import javax.annotation.Nullable; +import java.util.Optional; public class RootBuildCacheControllerRef { @@ -59,8 +59,7 @@ public boolean isEmitDebugLogging() { } @Override - @Nullable - public T load(BuildCacheLoadCommand command) { + public Optional load(BuildCacheLoadCommand command) { return delegate.load(command); } diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/service/BaseBuildCacheServiceHandle.java b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/service/BaseBuildCacheServiceHandle.java index 48ceedd44d235..aefe81e4beca6 100644 --- a/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/service/BaseBuildCacheServiceHandle.java +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/internal/controller/service/BaseBuildCacheServiceHandle.java @@ -16,17 +16,17 @@ package org.gradle.caching.internal.controller.service; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.caching.BuildCacheEntryReader; import org.gradle.caching.BuildCacheKey; import org.gradle.caching.BuildCacheService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; public class BaseBuildCacheServiceHandle implements BuildCacheServiceHandle { - private static final Logger LOGGER = Logging.getLogger(OpFiringBuildCacheServiceHandle.class); + private static final Logger LOGGER = LoggerFactory.getLogger(OpFiringBuildCacheServiceHandle.class); protected final BuildCacheService service; diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/local/internal/package-info.java b/subprojects/build-cache/src/main/java/org/gradle/caching/local/internal/package-info.java new file mode 100644 index 0000000000000..701443b7c03b5 --- /dev/null +++ b/subprojects/build-cache/src/main/java/org/gradle/caching/local/internal/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NonNullApi +package org.gradle.caching.local.internal; + +import org.gradle.api.NonNullApi; diff --git a/subprojects/build-cache/src/test/groovy/org/gradle/caching/internal/controller/DefaultBuildCacheControllerTest.groovy b/subprojects/build-cache/src/test/groovy/org/gradle/caching/internal/controller/DefaultBuildCacheControllerTest.groovy index 871f805949e36..9e44ac4f95ed3 100644 --- a/subprojects/build-cache/src/test/groovy/org/gradle/caching/internal/controller/DefaultBuildCacheControllerTest.groovy +++ b/subprojects/build-cache/src/test/groovy/org/gradle/caching/internal/controller/DefaultBuildCacheControllerTest.groovy @@ -43,6 +43,7 @@ class DefaultBuildCacheControllerTest extends Specification { def localPush = true def remote = Mock(BuildCacheService) def remotePush = true + def loadmetadata = Mock(Object) BuildCacheService legacyLocal = null @@ -71,7 +72,7 @@ class DefaultBuildCacheControllerTest extends Specification { @Override Object getMetadata() { - return null + return loadmetadata } } } diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/enforcerplugin/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/enforcerplugin/pom.xml index 30cc24b12a017..82ce123ea4d90 100644 --- a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/enforcerplugin/pom.xml +++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/enforcerplugin/pom.xml @@ -42,8 +42,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml index a1c5e72658e8a..b7a6212acd6e7 100644 --- a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml +++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/flatmultimodule/webinar-parent/pom.xml @@ -34,8 +34,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/pom.xml index c7d9e282bcedd..7c438834abc87 100644 --- a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/pom.xml +++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModule/pom.xml @@ -34,8 +34,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/pom.xml index 4b33064fd9894..43afc72e389dc 100644 --- a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/pom.xml +++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithNestedParent/pom.xml @@ -26,8 +26,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/pom.xml index 5866f29dc0eb7..ec71537a8a5f4 100644 --- a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/pom.xml +++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/multiModuleWithRemoteParent/pom.xml @@ -48,8 +48,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/pom.xml index d3de13ada3426..ea72edb34d1cd 100644 --- a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/pom.xml +++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/remoteparent/pom.xml @@ -24,8 +24,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/pom.xml index a9b66b3c97a20..a436695b48acf 100644 --- a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/pom.xml +++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/singleModule/pom.xml @@ -26,8 +26,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testsJar/pom.xml b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testsJar/pom.xml index 59bb99fbe5905..b51566a30e671 100644 --- a/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testsJar/pom.xml +++ b/subprojects/build-init/src/integTest/resources/org/gradle/buildinit/plugins/MavenConversionIntegrationTest/testsJar/pom.xml @@ -37,8 +37,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.6 - 1.6 + 1.7 + 1.7 diff --git a/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java b/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java index 3df52e658c8bd..2757c65ed00e0 100644 --- a/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java +++ b/subprojects/build-init/src/main/java/org/gradle/api/tasks/wrapper/Wrapper.java @@ -131,7 +131,7 @@ void generate() { generator.setExitEnvironmentVar("GRADLE_EXIT_CONSOLE"); generator.setAppNameSystemProperty("org.gradle.appname"); generator.setScriptRelPath(unixScript.getName()); - generator.setDefaultJvmOpts(ImmutableList.of("-Xmx64m")); + generator.setDefaultJvmOpts(ImmutableList.of("-Xmx64m", "-Xms64m")); generator.generateUnixScript(unixScript); generator.generateWindowsScript(getBatchScript()); } diff --git a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/library-versions.properties b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/library-versions.properties index 49dfcbd595b2b..b7504b2d24d66 100644 --- a/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/library-versions.properties +++ b/subprojects/build-init/src/main/resources/org/gradle/buildinit/tasks/templates/library-versions.properties @@ -1,13 +1,13 @@ #Generated file, please do not edit - Version values used in build-init templates commons-math=3.6.1 groovy=2.5.6 -guava=27.0.1-jre +guava=27.1-jre junit=4.12 kotlin=1.3.21 scala-library=2.12.8 scala-xml=1.1.1 scala=2.12 -scalatest=3.0.5 +scalatest=3.1.0-RC1 slf4j=1.7.26 spock=1.2-groovy-2.5 testng=6.14.3 diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/pmd/PmdPluginAuxclasspathIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/pmd/PmdPluginAuxclasspathIntegrationTest.groovy index 48a8a721cdfa3..f224a5d3750a9 100644 --- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/pmd/PmdPluginAuxclasspathIntegrationTest.groovy +++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/pmd/PmdPluginAuxclasspathIntegrationTest.groovy @@ -39,7 +39,7 @@ class PmdPluginAuxclasspathIntegrationTest extends AbstractPmdPluginVersionInteg apply plugin: 'java' - ${!TestPrecondition.FIX_TO_WORK_ON_JAVA9.fulfilled ? "sourceCompatibility = 1.6" : ""} + ${!TestPrecondition.FIX_TO_WORK_ON_JAVA9.fulfilled ? "sourceCompatibility = 1.7" : ""} } project("pmd-rule") { diff --git a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/pmd/PmdPluginVersionIntegrationTest.groovy b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/pmd/PmdPluginVersionIntegrationTest.groovy index 75ab91d7fa312..7b5e0fafb63f9 100644 --- a/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/pmd/PmdPluginVersionIntegrationTest.groovy +++ b/subprojects/code-quality/src/integTest/groovy/org/gradle/api/plugins/quality/pmd/PmdPluginVersionIntegrationTest.groovy @@ -44,7 +44,7 @@ class PmdPluginVersionIntegrationTest extends AbstractPmdPluginVersionIntegratio classpath = files() }"""} - ${!TestPrecondition.FIX_TO_WORK_ON_JAVA9.fulfilled ? "sourceCompatibility = 1.6" : ""} + ${!TestPrecondition.FIX_TO_WORK_ON_JAVA9.fulfilled ? "sourceCompatibility = 1.7" : ""} """.stripIndent() } diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java index 9da57d81feb9d..8630cc0899aa1 100644 --- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java +++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Checkstyle.java @@ -106,7 +106,7 @@ public IsolatedAntBuilder getAntBuilder() { * checkstyleTask { * reports { * html { - * destination "build/codenarc.html" + * destination "build/checkstyle.html" * } * } * } @@ -128,7 +128,7 @@ public CheckstyleReports reports(@DelegatesTo(value=CheckstyleReports.class, str * checkstyleTask { * reports { * html { - * destination "build/codenarc.html" + * destination "build/checkstyle.html" * } * } * } diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.java index af108dbc09702..0b396deaaab4a 100644 --- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.java +++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/Pmd.java @@ -147,7 +147,9 @@ public void setPmdClasspath(FileCollection pmdClasspath) { /** * The built-in rule sets to be used. See the official list of built-in rule sets. * - * Example: ruleSets = ["basic", "braces"] + *
+     *     ruleSets = ["basic", "braces"]
+     * 
*/ @Input public List getRuleSets() { @@ -157,7 +159,9 @@ public List getRuleSets() { /** * The built-in rule sets to be used. See the official list of built-in rule sets. * - * Example: ruleSets = ["basic", "braces"] + *
+     *     ruleSets = ["basic", "braces"]
+     * 
*/ public void setRuleSets(List ruleSets) { this.ruleSets = ruleSets; @@ -183,7 +187,9 @@ public void setTargetJdk(TargetJdk targetJdk) { * * See the official documentation for how to author a rule set. * - * Example: ruleSetConfig = resources.text.fromFile(resources.file("config/pmd/myRuleSets.xml")) + *
+     *     ruleSetConfig = resources.text.fromFile(resources.file("config/pmd/myRuleSets.xml"))
+     * 
* * @since 2.2 */ @@ -199,7 +205,9 @@ public TextResource getRuleSetConfig() { * * See the official documentation for how to author a rule set. * - * Example: ruleSetConfig = resources.text.fromFile(resources.file("config/pmd/myRuleSets.xml")) + *
+     *     ruleSetConfig = resources.text.fromFile(resources.file("config/pmd/myRuleSets.xml"))
+     * 
* * @since 2.2 */ @@ -209,8 +217,11 @@ public void setRuleSetConfig(@Nullable TextResource ruleSetConfig) { /** * The custom rule set files to be used. See the official documentation for how to author a rule set file. + * If you want to only use custom rule sets, you must clear {@code ruleSets}. * - * Example: ruleSetFiles = files("config/pmd/myRuleSets.xml") + *
+     *     ruleSetFiles = files("config/pmd/myRuleSet.xml")
+     * 
*/ @InputFiles @PathSensitive(PathSensitivity.NONE) @@ -225,8 +236,6 @@ public FileCollection getRuleSetFiles() { *
      *     ruleSetFiles = files("config/pmd/myRuleSets.xml")
      * 
- * - * If you want to only use custom rule sets, you must clear {@code ruleSets}. */ public void setRuleSetFiles(FileCollection ruleSetFiles) { this.ruleSetFiles = ruleSetFiles; @@ -243,7 +252,9 @@ public final PmdReports getReports() { /** * Whether or not to allow the build to continue if there are warnings. * - * Example: ignoreFailures = true + *
+     *     ignoreFailures = true
+     * 
*/ public boolean getIgnoreFailures() { return ignoreFailures; @@ -253,7 +264,9 @@ public boolean getIgnoreFailures() { /** * Whether or not to allow the build to continue if there are warnings. * - * Example: ignoreFailures = true + *
+     *     ignoreFailures = true
+     * 
*/ public void setIgnoreFailures(boolean ignoreFailures) { this.ignoreFailures = ignoreFailures; diff --git a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.java b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.java index c5af5fd56edb2..b035ba51917c6 100644 --- a/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.java +++ b/subprojects/code-quality/src/main/groovy/org/gradle/api/plugins/quality/PmdExtension.java @@ -47,7 +47,9 @@ public PmdExtension(Project project) { /** * The built-in rule sets to be used. See the official list of built-in rule sets. * - * Example: ruleSets = ["category/java/errorprone.xml", "category/java/bestpractices.xml"] + *
+     *     ruleSets = ["category/java/errorprone.xml", "category/java/bestpractices.xml"]
+     * 
*/ public List getRuleSets() { return ruleSets; @@ -56,7 +58,9 @@ public List getRuleSets() { /** * The built-in rule sets to be used. See the official list of built-in rule sets. * - * Example: ruleSets = ["category/java/errorprone.xml", "category/java/bestpractices.xml"] + *
+     *     ruleSets = ["category/java/errorprone.xml", "category/java/bestpractices.xml"]
+     * 
*/ public void setRuleSets(List ruleSets) { this.ruleSets = ruleSets; @@ -65,7 +69,9 @@ public void setRuleSets(List ruleSets) { /** * Convenience method for adding rule sets. * - * Example: ruleSets "category/java/errorprone.xml", "category/java/bestpractices.xml" + *
+     *     ruleSets "category/java/errorprone.xml", "category/java/bestpractices.xml"
+     * 
* * @param ruleSets the rule sets to be added */ @@ -106,7 +112,9 @@ public void setTargetJdk(Object value) { * * See the official documentation for the list of priorities. * - * Example: rulePriority = 3 + *
+     *     rulePriority = 3
+     * 
*/ public int getRulePriority() { return rulePriority; @@ -125,7 +133,9 @@ public void setRulePriority(int intValue) { * * See the official documentation for how to author a rule set. * - * Example: ruleSetConfig = resources.text.fromFile("config/pmd/myRuleSet.xml") + *
+     *     ruleSetConfig = resources.text.fromFile("config/pmd/myRuleSet.xml")
+     * 
* * @since 2.2 */ @@ -139,7 +149,9 @@ public TextResource getRuleSetConfig() { * * See the official documentation for how to author a rule set. * - * Example: ruleSetConfig = resources.text.fromFile("config/pmd/myRuleSet.xml") + *
+     *     ruleSetConfig = resources.text.fromFile("config/pmd/myRuleSet.xml")
+     * 
* * @since 2.2 */ @@ -149,8 +161,11 @@ public void setRuleSetConfig(@Nullable TextResource ruleSetConfig) { /** * The custom rule set files to be used. See the official documentation for how to author a rule set file. + * If you want to only use custom rule sets, you must clear {@code ruleSets}. * - * Example: ruleSetFiles = files("config/pmd/myRuleSet.xml") + *
+     *     ruleSetFiles = files("config/pmd/myRuleSet.xml")
+     * 
*/ public FileCollection getRuleSetFiles() { return ruleSetFiles; @@ -163,8 +178,6 @@ public FileCollection getRuleSetFiles() { *
      *     ruleSetFiles = files("config/pmd/myRuleSets.xml")
      * 
- * - * If you want to only use custom rule sets, you must clear {@code ruleSets}. */ public void setRuleSetFiles(FileCollection ruleSetFiles) { this.ruleSetFiles = project.getObjects().fileCollection().from(ruleSetFiles); @@ -173,7 +186,9 @@ public void setRuleSetFiles(FileCollection ruleSetFiles) { /** * Convenience method for adding rule set files. * - * Example: ruleSetFiles "config/pmd/myRuleSet.xml" + *
+     *     ruleSetFiles "config/pmd/myRuleSet.xml"
+     * 
* * @param ruleSetFiles the rule set files to be added */ diff --git a/subprojects/composite-builds/src/integTest/groovy/org/gradle/integtests/composite/CompositeBuildArtifactTransformIntegrationTest.groovy b/subprojects/composite-builds/src/integTest/groovy/org/gradle/integtests/composite/CompositeBuildArtifactTransformIntegrationTest.groovy index 037ef25214eeb..d092f49a8f389 100644 --- a/subprojects/composite-builds/src/integTest/groovy/org/gradle/integtests/composite/CompositeBuildArtifactTransformIntegrationTest.groovy +++ b/subprojects/composite-builds/src/integTest/groovy/org/gradle/integtests/composite/CompositeBuildArtifactTransformIntegrationTest.groovy @@ -16,13 +16,8 @@ package org.gradle.integtests.composite -import org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner import org.gradle.test.fixtures.file.TestFile -import org.junit.runner.RunWith -import static org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner.configureIncrementalArtifactTransformations - -@RunWith(ExperimentalIncrementalArtifactTransformationsRunner) class CompositeBuildArtifactTransformIntegrationTest extends AbstractCompositeBuildIntegrationTest { def "can apply a transform to the outputs of included builds"() { @@ -36,7 +31,6 @@ class CompositeBuildArtifactTransformIntegrationTest extends AbstractCompositeBu apply plugin: 'java' """ } - configureIncrementalArtifactTransformations(buildA.settingsFile) includedBuilds << buildB includedBuilds << buildC @@ -84,6 +78,6 @@ class CompositeBuildArtifactTransformIntegrationTest extends AbstractCompositeBu } private String expectedWorkspaceLocation(TestFile includedBuild) { - ExperimentalIncrementalArtifactTransformationsRunner.incrementalArtifactTransformations ? includedBuild.file("build/transforms") : executer.gradleUserHomeDir + includedBuild.file("build/transforms") } } diff --git a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/CompositeBuildDependencySubstitutions.java b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/CompositeBuildDependencySubstitutions.java index e1f9812955221..45b2d8a95b8a4 100644 --- a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/CompositeBuildDependencySubstitutions.java +++ b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/CompositeBuildDependencySubstitutions.java @@ -82,13 +82,15 @@ private ProjectComponentIdentifier getReplacementFor(ModuleComponentSelector sel LOGGER.info("Found project '" + match + "' as substitute for module '" + candidateId + "'."); return match; } - SortedSet sortedProjects = Sets.newTreeSet(CollectionUtils.collect(providingProjects, new Transformer() { - @Override - public String transform(ProjectComponentIdentifier projectComponentIdentifier) { - return projectComponentIdentifier.getDisplayName(); - } - })); - String failureMessage = String.format("Module version '%s' is not unique in composite: can be provided by %s.", selector.getDisplayName(), sortedProjects); - throw new ModuleVersionResolveException(selector, failureMessage); + throw new ModuleVersionResolveException(selector, () -> { + SortedSet sortedProjects = Sets.newTreeSet(CollectionUtils.collect(providingProjects, new Transformer() { + @Override + public String transform(ProjectComponentIdentifier projectComponentIdentifier) { + return projectComponentIdentifier.getDisplayName(); + } + })); + + return String.format("Module version '%s' is not unique in composite: can be provided by %s.", selector.getDisplayName(), sortedProjects); + }); } } diff --git a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuildTaskGraph.java b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuildTaskGraph.java index 6413781409d52..25b7c3979e709 100644 --- a/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuildTaskGraph.java +++ b/subprojects/composite-builds/src/main/java/org/gradle/composite/internal/DefaultIncludedBuildTaskGraph.java @@ -73,7 +73,7 @@ private void checkNoCycles(BuildIdentifier sourceBuild, BuildIdentifier targetBu if (sourceBuild.equals(nextTarget)) { candidateCycle.add(nextTarget); ProjectComponentSelector selector = new DefaultProjectComponentSelector(candidateCycle.get(0), Path.ROOT, Path.ROOT, ":", ImmutableAttributes.EMPTY, Collections.emptyList()); - throw new ModuleVersionResolveException(selector, "Included build dependency cycle: " + reportCycle(candidateCycle)); + throw new ModuleVersionResolveException(selector, () -> "Included build dependency cycle: " + reportCycle(candidateCycle)); } checkNoCycles(sourceBuild, nextTarget, candidateCycle); diff --git a/subprojects/core-api/core-api.gradle.kts b/subprojects/core-api/core-api.gradle.kts index 7e0fa843896b3..a873b83be2369 100644 --- a/subprojects/core-api/core-api.gradle.kts +++ b/subprojects/core-api/core-api.gradle.kts @@ -1,5 +1,5 @@ -import org.gradle.gradlebuild.unittestandcompile.ModuleType import org.gradle.gradlebuild.testing.integrationtests.cleanup.WhenNotEmpty +import org.gradle.gradlebuild.unittestandcompile.ModuleType /* * Copyright 2017 the original author or authors. @@ -32,7 +32,7 @@ dependencies { implementation(library("ant")) implementation(library("commons_io")) implementation(library("commons_lang")) - implementation(library("jcip")) + implementation(library("inject")) testFixturesImplementation(project(":internalTesting")) } diff --git a/subprojects/core-api/src/main/java/org/gradle/api/Project.java b/subprojects/core-api/src/main/java/org/gradle/api/Project.java index 46f0ea07df8fb..12543caf28342 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/Project.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/Project.java @@ -1551,7 +1551,7 @@ public interface Project extends Comparable, ExtensionAware, PluginAwar * copy the files. Example: *
      * copy {
-     *    from configurations.runtime
+     *    from configurations.runtimeClasspath
      *    into 'build/deploy/lib'
      * }
      * 
diff --git a/subprojects/core-api/src/main/java/org/gradle/api/Script.java b/subprojects/core-api/src/main/java/org/gradle/api/Script.java index 0dfa568dcd092..a6e44a2a39e85 100755 --- a/subprojects/core-api/src/main/java/org/gradle/api/Script.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/Script.java @@ -256,7 +256,7 @@ public interface Script { * is then used to copy the files. Example: *
      * copy {
-     *    from configurations.runtime
+     *    from configurations.runtimeClasspath
      *    into 'build/deploy/lib'
      * }
      * 
diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ConfigurationContainer.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ConfigurationContainer.java index 9ff18b2586575..d3a90eef3381c 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ConfigurationContainer.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ConfigurationContainer.java @@ -49,32 +49,36 @@ * An example showing how to refer to a given configuration by name * in order to get hold of all dependencies (e.g. jars, but only) *
- *   apply plugin: 'java' //so that I can use 'compile' configuration
+ *   apply plugin: 'java' //so that I can use 'implementation', 'compileClasspath' configuration
  *
- *   //copying all dependencies attached to 'compile' into a specific folder
+ *   dependencies {
+ *       implementation 'org.slf4j:slf4j-api:1.7.26'
+ *   }
+ *
+ *   //copying all dependencies attached to 'compileClasspath' into a specific folder
  *   task copyAllDependencies(type: Copy) {
- *     //referring to the 'compile' configuration
- *     from configurations.compile
+ *     //referring to the 'compileClasspath' configuration
+ *     from configurations.compileClasspath
  *     into 'allLibs'
  *   }
  * 
* * An example showing how to declare and configure configurations *
- * apply plugin: 'java' //so that I can use 'compile', 'testCompile' configurations
+ * apply plugin: 'java' //so that I can use 'implementation', 'testImplementation' configurations
  *
  * configurations {
  *   //adding a configuration:
  *   myConfiguration
  *
  *   //adding a configuration that extends existing configuration:
- *   //(testCompile was added by the java plugin)
- *   myIntegrationTestsCompile.extendsFrom(testCompile)
+ *   //(testImplementation was added by the java plugin)
+ *   myIntegrationTestsCompile.extendsFrom(testImplementation)
  *
  *   //configuring existing configurations not to put transitive dependencies on the compile classpath
  *   //this way you can avoid issues with implicit dependencies to transitive libraries
- *   compile.transitive = false
- *   testCompile.transitive = false
+ *   compileClasspath.transitive = false
+ *   testCompileClasspath.transitive = false
  * }
  * 
* diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ModuleDependency.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ModuleDependency.java index 4ef7b84d32e22..995155db68e68 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ModuleDependency.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ModuleDependency.java @@ -54,10 +54,10 @@ public interface ModuleDependency extends Dependency, HasConfigurableAttributes< * then consider using forced versions' feature: {@link ResolutionStrategy#force(Object...)}. * *
-     * apply plugin: 'java' //so that I can declare 'compile' dependencies
+     * apply plugin: 'java' //so that I can declare 'implementation' dependencies
      *
      * dependencies {
-     *   compile('org.hibernate:hibernate:3.1') {
+     *   implementation('org.hibernate:hibernate:3.1') {
      *     //excluding a particular transitive dependency:
      *     exclude module: 'cglib' //by artifact name
      *     exclude group: 'org.jmock' //by group
diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ResolutionStrategy.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ResolutionStrategy.java
index 26ac327a3880d..cf961d411e75d 100644
--- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ResolutionStrategy.java
+++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/ResolutionStrategy.java
@@ -171,7 +171,7 @@ public interface ResolutionStrategy {
      * Example:
      * 
      * configurations {
-     *   compile.resolutionStrategy {
+     *   compileClasspath.resolutionStrategy {
      *     eachDependency { DependencyResolveDetails details ->
      *       //specifying a fixed version for all libraries with 'org.gradle' group
      *       if (details.requested.group == 'org.gradle') {
diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/DependencyHandler.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/DependencyHandler.java
index 0ffd7908ae9e2..7dd434a431d34 100644
--- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/DependencyHandler.java
+++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/DependencyHandler.java
@@ -20,8 +20,8 @@
 import org.gradle.api.Incubating;
 import org.gradle.api.artifacts.Dependency;
 import org.gradle.api.artifacts.query.ArtifactResolutionQuery;
-import org.gradle.api.artifacts.transform.ParameterizedTransformSpec;
 import org.gradle.api.artifacts.transform.TransformAction;
+import org.gradle.api.artifacts.transform.TransformParameters;
 import org.gradle.api.artifacts.transform.TransformSpec;
 import org.gradle.api.artifacts.transform.VariantTransform;
 import org.gradle.api.artifacts.type.ArtifactTypeContainer;
@@ -38,29 +38,29 @@
  *
  * 
  * dependencies {
- *     configurationName dependencyNotation1, dependencyNotation2, ...
+ *     configurationName dependencyNotation
  * }
  * 
* *

Example shows a basic way of declaring dependencies. *

  * apply plugin: 'java'
- * //so that we can use 'compile', 'testCompile' for dependencies
+ * //so that we can use 'implementation', 'testImplementation' for dependencies
  *
  * dependencies {
  *   //for dependencies found in artifact repositories you can use
  *   //the group:name:version notation
- *   compile 'commons-lang:commons-lang:2.6'
- *   testCompile 'org.mockito:mockito:1.9.0-rc1'
+ *   implementation 'commons-lang:commons-lang:2.6'
+ *   testImplementation 'org.mockito:mockito:1.9.0-rc1'
  *
  *   //map-style notation:
- *   compile group: 'com.google.code.guice', name: 'guice', version: '1.0'
+ *   implementation group: 'com.google.code.guice', name: 'guice', version: '1.0'
  *
  *   //declaring arbitrary files as dependencies
- *   compile files('hibernate.jar', 'libs/spring.jar')
+ *   implementation files('hibernate.jar', 'libs/spring.jar')
  *
  *   //putting all jars from 'libs' onto compile classpath
- *   compile fileTree('libs')
+ *   implementation fileTree('libs')
  * }
  * 
* @@ -86,10 +86,10 @@ * * *
- * apply plugin: 'java' //so that I can declare 'compile' dependencies
+ * apply plugin: 'java' //so that I can declare 'implementation' dependencies
  *
  * dependencies {
- *   compile('org.hibernate:hibernate:3.1') {
+ *   implementation('org.hibernate:hibernate:3.1') {
  *     //in case of versions conflict '3.1' version of hibernate wins:
  *     force = true
  *
@@ -111,14 +111,14 @@
  * 
  *
  * 
- * apply plugin: 'java' //so that I can declare 'compile' dependencies
+ * apply plugin: 'java' //so that I can declare 'implementation' dependencies
  *
  * dependencies {
  *   //configuring dependency to specific configuration of the module
- *   compile configuration: 'someConf', group: 'org.someOrg', name: 'someModule', version: '1.0'
+ *   implementation configuration: 'someConf', group: 'org.someOrg', name: 'someModule', version: '1.0'
  *
  *   //configuring dependency on 'someLib' module
- *   compile(group: 'org.myorg', name: 'someLib', version:'1.0') {
+ *   implementation(group: 'org.myorg', name: 'someLib', version:'1.0') {
  *     //explicitly adding the dependency artifact:
  *     artifact {
  *       //useful when some artifact properties unconventional
@@ -161,16 +161,16 @@
  *
  * 
  * apply plugin: 'java'
- * //so that we can use 'compile', 'testCompile' for dependencies
+ * //so that we can use 'implementation', 'testImplementation' for dependencies
  *
  * dependencies {
  *   //for dependencies found in artifact repositories you can use
  *   //the string notation, e.g. group:name:version
- *   compile 'commons-lang:commons-lang:2.6'
- *   testCompile 'org.mockito:mockito:1.9.0-rc1'
+ *   implementation 'commons-lang:commons-lang:2.6'
+ *   testImplementation 'org.mockito:mockito:1.9.0-rc1'
  *
  *   //map notation:
- *   compile group: 'com.google.code.guice', name: 'guice', version: '1.0'
+ *   implementation group: 'com.google.code.guice', name: 'guice', version: '1.0'
  * }
  * 
* @@ -195,14 +195,14 @@ * *
  * apply plugin: 'java'
- * //so that we can use 'compile', 'testCompile' for dependencies
+ * //so that we can use 'implementation', 'testImplementation' for dependencies
  *
  * dependencies {
  *   //declaring arbitrary files as dependencies
- *   compile files('hibernate.jar', 'libs/spring.jar')
+ *   implementation files('hibernate.jar', 'libs/spring.jar')
  *
  *   //putting all jars from 'libs' onto compile classpath
- *   compile fileTree('libs')
+ *   implementation fileTree('libs')
  * }
  * 
* @@ -225,17 +225,17 @@ *
  * //Our Gradle plugin is written in groovy
  * apply plugin: 'groovy'
- * //now we can use the 'compile' configuration for declaring dependencies
+ * //now we can use the 'implementation' configuration for declaring dependencies
  *
  * dependencies {
  *   //we will use the Groovy version that ships with Gradle:
- *   compile localGroovy()
+ *   implementation localGroovy()
  *
  *   //our plugin requires Gradle API interfaces and classes to compile:
- *   compile gradleApi()
+ *   implementation gradleApi()
  *
  *   //we will use the Gradle test-kit to test build logic:
- *   testCompile gradleTestKit()
+ *   testImplementation gradleTestKit()
  * }
  * 
* @@ -445,15 +445,6 @@ public interface DependencyHandler { */ void registerTransform(Action registrationAction); - /** - * Registers an artifact transform with a parameter object. - * - * @see TransformAction - * @since 5.2 - */ - @Incubating - void registerTransform(Class parameterType, Action> registrationAction); - /** * Registers an artifact transform without a parameter object. * @@ -461,7 +452,7 @@ public interface DependencyHandler { * @since 5.3 */ @Incubating - void registerTransformAction(Class actionType, Action registrationAction); + void registerTransform(Class> actionType, Action> registrationAction); /** * Declares a dependency on a platform. If the target coordinates represent multiple diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/RepositoryHandler.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/RepositoryHandler.java index 0ac627037d7b0..9ffdec18e8df9 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/RepositoryHandler.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/dsl/RepositoryHandler.java @@ -89,6 +89,16 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer { */ @Incubating ArtifactRepository gradlePluginPortal(); + + /** + * Adds a repository which looks in Gradle Central Plugin Repository for dependencies. + * + * @param action a configuration action + * @return the added resolver + * @since 5.4 + */ + @Incubating + ArtifactRepository gradlePluginPortal(Action action); /** * Adds a repository which looks in Bintray's JCenter repository for dependencies. @@ -182,6 +192,24 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer { */ MavenArtifactRepository mavenCentral(); + /** + * Adds a repository which looks in the Maven central repository for dependencies. The URL used to access this repository is + * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#MAVEN_CENTRAL_URL}. The name of the repository is + * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_CENTRAL_REPO_NAME}. + * + *

Examples:

+ *
+     * repositories {
+     *     mavenCentral()
+     * }
+     * 
+ * + * @param action a configuration action + * @return the added resolver + * @since 5.3 + */ + MavenArtifactRepository mavenCentral(Action action); + /** * Adds a repository which looks in the local Maven cache for dependencies. The name of the repository is * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_LOCAL_REPO_NAME}. @@ -206,6 +234,32 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer { */ MavenArtifactRepository mavenLocal(); + /** + * Adds a repository which looks in the local Maven cache for dependencies. The name of the repository is + * {@value org.gradle.api.artifacts.ArtifactRepositoryContainer#DEFAULT_MAVEN_LOCAL_REPO_NAME}. + * + *

Examples:

+ *
+     * repositories {
+     *     mavenLocal()
+     * }
+     * 
+ *

+ * The location for the repository is determined as follows (in order of precedence): + *

+ *
    + *
  1. The value of system property 'maven.repo.local' if set;
  2. + *
  3. The value of element <localRepository> of ~/.m2/settings.xml if this file exists and element is set;
  4. + *
  5. The value of element <localRepository> of $M2_HOME/conf/settings.xml (where $M2_HOME is the value of the environment variable with that name) if this file exists and element is set;
  6. + *
  7. The path ~/.m2/repository.
  8. + *
+ * + * @param action a configuration action + * @return the added resolver + * @since 5.3 + */ + MavenArtifactRepository mavenLocal(Action action); + /** * Adds a repository which looks in Google's Maven repository for dependencies. *

@@ -223,6 +277,24 @@ public interface RepositoryHandler extends ArtifactRepositoryContainer { */ MavenArtifactRepository google(); + /** + * Adds a repository which looks in Google's Maven repository for dependencies. + *

+ * The URL used to access this repository is {@literal "https://dl.google.com/dl/android/maven2/"}. + *

+ * Examples: + *

+     * repositories {
+     *     google()
+     * }
+     * 
+ * + * @param action a configuration action + * @return the added resolver + * @since 5.3 + */ + MavenArtifactRepository google(Action action); + /** * Adds and configures a Maven repository. Newly created instance of {@code MavenArtifactRepository} is passed as an argument to the closure. * diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/query/ArtifactResolutionQuery.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/query/ArtifactResolutionQuery.java index 5b86d0992c14a..6f20250d79225 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/query/ArtifactResolutionQuery.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/query/ArtifactResolutionQuery.java @@ -31,7 +31,7 @@ * * task resolveCompileSources { * doLast { - * def componentIds = configurations.compile.incoming.resolutionResult.allDependencies.collect { it.selected.id } + * def componentIds = configurations.compileClasspath.incoming.resolutionResult.allDependencies.collect { it.selected.id } * * def result = dependencies.createArtifactResolutionQuery() * .forComponents(componentIds) diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/ArtifactTransformException.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/ArtifactTransformException.java index 5f3bbd34fd767..8e852dfed6c2b 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/ArtifactTransformException.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/ArtifactTransformException.java @@ -29,6 +29,7 @@ * * @since 3.4 */ +@Deprecated @Contextual public class ArtifactTransformException extends GradleException { diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/CacheableTransformAction.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/CacheableTransform.java similarity index 95% rename from subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/CacheableTransformAction.java rename to subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/CacheableTransform.java index e1d510ddd6638..4b1dd5ef91ce9 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/CacheableTransformAction.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/CacheableTransform.java @@ -26,12 +26,12 @@ /** *

Attached to an {@link TransformAction} type to indicate that the build cache should be used for artifact transforms of this type.

* - *

Only an artifact transform that produces reproducible and relocatable outputs should be marked with {@code CacheableTransformAction}.

+ *

Only an artifact transform that produces reproducible and relocatable outputs should be marked with {@code CacheableTransform}.

* * @since 5.3 */ @Incubating @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) -public @interface CacheableTransformAction { +public @interface CacheableTransform { } diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/InputArtifact.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/InputArtifact.java index df27d71c1540d..478a3564dccb4 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/InputArtifact.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/InputArtifact.java @@ -17,6 +17,7 @@ package org.gradle.api.artifacts.transform; import org.gradle.api.Incubating; +import org.gradle.api.provider.Provider; import org.gradle.api.reflect.InjectionPointQualifier; import java.io.File; @@ -29,12 +30,16 @@ /** * Attached to a property that should receive the input artifact for an artifact transform. This is the artifact that the transform should be applied to. * + *

+ * The input artifact can be injected as a plain {@link File} or a {@link Provider}<{@link org.gradle.api.file.FileSystemLocation}>. + *

+ * * @since 5.3 */ @Incubating @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) @Documented -@InjectionPointQualifier(supportedTypes = File.class) +@InjectionPointQualifier(supportedTypes = { File.class, Provider.class }) public @interface InputArtifact { } diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformAction.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformAction.java index 171237dbaf449..ac6d820260330 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformAction.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformAction.java @@ -18,6 +18,8 @@ import org.gradle.api.Incubating; +import javax.inject.Inject; + /** * Interface for artifact transform actions. * @@ -25,16 +27,22 @@ * *

Implementations can receive parameters by using annotated abstract getter methods.

* - *

A property annotated with {@link TransformParameters} will receive the object provided by {@link ParameterizedTransformSpec#getParameters()}. - * *

A property annotated with {@link InputArtifact} will receive the input artifact location, which is the file or directory that the transform should be applied to. * *

A property annotated with {@link InputArtifactDependencies} will receive the dependencies of its input artifact. * + * @param Parameter type for the transform action. Should be {@link TransformParameters.None} if the action does not have parameters. * @since 5.3 */ @Incubating -public interface TransformAction { +public interface TransformAction { + + /** + * The object provided by {@link TransformSpec#getParameters()}. + */ + @Inject + T getParameters(); + /** * Executes the transform. * diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformParameters.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformParameters.java index 1107d99ab276a..72bc2c266f7a4 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformParameters.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformParameters.java @@ -17,23 +17,21 @@ package org.gradle.api.artifacts.transform; import org.gradle.api.Incubating; -import org.gradle.api.reflect.InjectionPointQualifier; - -import java.lang.annotation.Documented; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; /** - * Attached to a property that should receive the parameter object of the artifact transform. + * Marker interface for parameter objects to {@link TransformAction}s. * * @since 5.3 */ @Incubating -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.METHOD}) -@Documented -@InjectionPointQualifier -public @interface TransformParameters { +public interface TransformParameters { + /** + * Used for {@link TransformAction}s without parameters. + * + * @since 5.3 + */ + @Incubating + final class None implements TransformParameters { + private None() {} + } } diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformSpec.java b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformSpec.java index 68fc1db97a93d..1c5b61f3271e0 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformSpec.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/TransformSpec.java @@ -16,16 +16,18 @@ package org.gradle.api.artifacts.transform; +import org.gradle.api.Action; import org.gradle.api.Incubating; import org.gradle.api.attributes.AttributeContainer; /** * Base configuration for artifact transform registrations. * + * @param The transform specific parameter type. * @since 5.3 */ @Incubating -public interface TransformSpec { +public interface TransformSpec { /** * Attributes that match the variant that is consumed. */ @@ -35,4 +37,14 @@ public interface TransformSpec { * Attributes that match the variant that is produced. */ AttributeContainer getTo(); + + /** + * The parameters for the transform action. + */ + T getParameters(); + + /** + * Configure the parameters for the transform action. + */ + void parameters(Action action); } diff --git a/subprojects/core-api/src/main/java/org/gradle/api/attributes/Attribute.java b/subprojects/core-api/src/main/java/org/gradle/api/attributes/Attribute.java index 79d0ef67261ba..d84db6fbd3f49 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/attributes/Attribute.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/attributes/Attribute.java @@ -49,7 +49,7 @@ public static Attribute of(String name, Class type) { } /** - * Creates a new attribute of the given type, inferring the name of the attribute from the simple type name. + * Creates a new attribute of the given type, inferring the name of the attribute from the simple type name. * This method is useful when there's supposedly only one attribute of a specific type in a container, so there's * no need to distinguish by name (but the returned type doesn't enforce it_. There's no guarantee that subsequent * calls to this method with the same attributes would either return the same instance or different instances diff --git a/subprojects/core-api/src/main/java/org/gradle/api/attributes/Category.java b/subprojects/core-api/src/main/java/org/gradle/api/attributes/Category.java new file mode 100644 index 0000000000000..0ab28cc715059 --- /dev/null +++ b/subprojects/core-api/src/main/java/org/gradle/api/attributes/Category.java @@ -0,0 +1,54 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.attributes; + +import org.gradle.api.Incubating; +import org.gradle.api.Named; + +/** + * This attribute describes the categories of variants for a given module. + *

+ * Two values are found in published components: + *

    + *
  • {@code library}: Indicates that the variant is a library, that usually means a binary and a set of dependencies
  • + *
  • {@code platform}: indicates that the variant is a platform, that usually means a definition of dependency constraints
  • + *
+ * One value is used for derivation. A {@code platform} variant can be consumed as a {@code enforced-platform} which means all the dependency + * information it provides is applied as {@code forced}. + * + * @since 5.3 + */ +@Incubating +public interface Category extends Named { + + Attribute CATEGORY_ATTRIBUTE = Attribute.of("org.gradle.category", Category.class); + + /** + * The library category + */ + String LIBRARY = "library"; + + /** + * The platform category + */ + String REGULAR_PLATFORM = "platform"; + + /** + * The enforced platform, usually a synthetic variant derived from the {@code platform} + */ + String ENFORCED_PLATFORM = "enforced-platform"; +} diff --git a/subprojects/core-api/src/main/java/org/gradle/api/attributes/java/TargetJvmVersion.java b/subprojects/core-api/src/main/java/org/gradle/api/attributes/java/TargetJvmVersion.java new file mode 100644 index 0000000000000..c3403146756c2 --- /dev/null +++ b/subprojects/core-api/src/main/java/org/gradle/api/attributes/java/TargetJvmVersion.java @@ -0,0 +1,34 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.api.attributes.java; + +import org.gradle.api.Incubating; +import org.gradle.api.attributes.Attribute; + +/** + * Represents the target version of a Java library or platform. The target level is expected to correspond + * to a Java platform version number (integer). For example, "5" for Java 5, "8" for Java 8, or "11" for Java 11. + * + * @since 5.3 + */ +@Incubating +public interface TargetJvmVersion { + + /** + * The minimal target version for a Java library. Any consumer below this version would not be able to consume it. + */ + Attribute TARGET_JVM_VERSION_ATTRIBUTE = Attribute.of("org.gradle.jvm.version", Integer.class); +} diff --git a/subprojects/core-api/src/main/java/org/gradle/api/component/ConfigurationVariantDetails.java b/subprojects/core-api/src/main/java/org/gradle/api/component/ConfigurationVariantDetails.java index 78f7850b945e1..40c73aa2e9004 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/component/ConfigurationVariantDetails.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/component/ConfigurationVariantDetails.java @@ -38,6 +38,16 @@ public interface ConfigurationVariantDetails { */ void skip(); + /** + * Provides information about the optional status of this added configuration variant. + * + *
    + *
  • For the Maven world, this means that dependencies will be declared as {@code optional}.
  • + *
  • For the Ivy world, this means that configuration marked optional will not be extended by the {@code default} configuration.
  • + *
+ */ + void mapToOptional(); + /** * Provides information about how to publish to a Maven POM file. If * nothing is said, Gradle will publish all dependencies from this @@ -57,9 +67,8 @@ public interface ConfigurationVariantDetails { * * The mapping only applies to dependencies: dependency constraints will * systematically be published as import scope. + * @param scope the Maven scope to use for dependencies found in this configuration variant * - * @param scope the Maven scope to use for dependencies found in this configuration variant - * @param optional true if the dependencies found in this configuration variant should be published as optional */ - void mapToMavenScope(String scope, boolean optional); + void mapToMavenScope(String scope); } diff --git a/subprojects/core-api/src/main/java/org/gradle/api/file/DirectoryProperty.java b/subprojects/core-api/src/main/java/org/gradle/api/file/DirectoryProperty.java index 0707d9969f38f..a74a3c29b3fd3 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/file/DirectoryProperty.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/file/DirectoryProperty.java @@ -21,6 +21,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import javax.annotation.Nullable; import java.io.File; /** @@ -49,7 +50,7 @@ public interface DirectoryProperty extends Property { /** * Sets the location of this directory. */ - void set(File dir); + void set(@Nullable File dir); /** * {@inheritDoc} diff --git a/subprojects/core-api/src/main/java/org/gradle/api/file/RegularFileProperty.java b/subprojects/core-api/src/main/java/org/gradle/api/file/RegularFileProperty.java index 873245e568d07..f786f6e2ac106 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/file/RegularFileProperty.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/file/RegularFileProperty.java @@ -21,6 +21,7 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import javax.annotation.Nullable; import java.io.File; /** @@ -44,7 +45,7 @@ public interface RegularFileProperty extends Property { /** * Sets the location of this file, using a {@link File} instance. */ - void set(File file); + void set(@Nullable File file); /** * {@inheritDoc} diff --git a/subprojects/core-api/src/main/java/org/gradle/api/model/ReplacedBy.java b/subprojects/core-api/src/main/java/org/gradle/api/model/ReplacedBy.java new file mode 100644 index 0000000000000..83aca72a6a3fe --- /dev/null +++ b/subprojects/core-api/src/main/java/org/gradle/api/model/ReplacedBy.java @@ -0,0 +1,49 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.model; + +import org.gradle.api.Incubating; +import org.gradle.api.tasks.Internal; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + *

Attached to a task property to indicate that the property has been replaced by another. Like {@link Internal}, the property is ignored during up-to-date checks.

+ * + *

This annotation should be attached to the getter method in Java or the property field in Groovy. You should also consider adding {@link Deprecated} to any replaced property.

+ * + *

This will cause the task not to be considered out-of-date when the property has changed.

+ * + * @since 5.4 + */ +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +@Incubating +public @interface ReplacedBy { + /** + * The Java Bean-style name of the replacement property. + *

+ * If the property has been replaced with a method named {@code getFooBar()}, then this should be {@code fooBar}. + *

+ */ + String value(); +} diff --git a/subprojects/core-api/src/main/java/org/gradle/api/tasks/Classpath.java b/subprojects/core-api/src/main/java/org/gradle/api/tasks/Classpath.java index b66a0a215f028..f17caef1d7a6a 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/tasks/Classpath.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/tasks/Classpath.java @@ -28,6 +28,13 @@ *

This annotation should be attached to the getter method in Java or the property in Groovy. * Annotations on setters or just the field in Java are ignored.

* + *

+ * For jar files, the normalized path is empty. + * The content of the jar file is normalized so that time stamps and order of the zip entries in the jar file do not matter. + * If a directory is a classpath entry, then the root directory itself is ignored. + * The files in the directory are sorted and the relative path to the root directory is used as normalized path. + *

+ * *

Note: to stay compatible with versions prior to Gradle 3.2, classpath * properties need to be annotated with {@literal @}{@link InputFiles} as well.

* diff --git a/subprojects/core-api/src/main/java/org/gradle/api/tasks/PathSensitivity.java b/subprojects/core-api/src/main/java/org/gradle/api/tasks/PathSensitivity.java index 62c841e1cdecf..5fac5908e2c0d 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/tasks/PathSensitivity.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/tasks/PathSensitivity.java @@ -33,6 +33,19 @@ public enum PathSensitivity { /** * Use the location of the file related to a hierarchy. + * + *

+ * For files in the root of the file collection, the file name is used as the normalized path. + * For directories in the root of the file collection, an empty string is used as normalized path. + * For files in directories in the root of the file collection, the normalized path is the relative path of the file to the root directory containing it. + *

+ * + *
+ * Example: The property is an input directory. + *
    + *
  • The path of the input directory is ignored.
  • + *
  • The path of the files in the input directory are considered relative to the input directory.
  • + *
*/ RELATIVE, diff --git a/subprojects/core-api/src/main/java/org/gradle/api/tasks/SkipWhenEmpty.java b/subprojects/core-api/src/main/java/org/gradle/api/tasks/SkipWhenEmpty.java index af1b0c4d945c6..4cc37c151e6bc 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/tasks/SkipWhenEmpty.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/tasks/SkipWhenEmpty.java @@ -15,7 +15,11 @@ */ package org.gradle.api.tasks; -import java.lang.annotation.*; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** *

Attached to a task property to indicate that the task should be skipped when the value of the property is an empty @@ -23,6 +27,8 @@ * *

If all of the inputs declared with this annotation are empty, the task will be skipped with a "NO-SOURCE" message.

* + *

Inputs annotated with this annotation can be queried for changes via {@link org.gradle.work.InputChanges#getFileChanges(org.gradle.api.file.FileCollection)} or {@link org.gradle.work.InputChanges#getFileChanges(org.gradle.api.provider.Provider)}.

+ * *

This annotation should be attached to the getter method in Java or the property in Groovy. * Annotations on setters or just the field in Java are ignored.

* diff --git a/subprojects/core-api/src/main/java/org/gradle/api/tasks/TaskOutputs.java b/subprojects/core-api/src/main/java/org/gradle/api/tasks/TaskOutputs.java index 2829db8394ddc..6ac2dc1e89e39 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/tasks/TaskOutputs.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/tasks/TaskOutputs.java @@ -30,24 +30,35 @@ @HasInternalProtocol public interface TaskOutputs { /** - *

Adds a predicate to determine whether the outputs of this task are up-to-date. The given closure is executed - * at task execution time. The closure is passed the task as a parameter. If the closure returns false, the task - * outputs are considered out-of-date and the task will be executed.

- * - *

You can add multiple such predicates. The task outputs are considered out-of-date when any predicate returns - * false.

+ *

+ * Adds a predicate to determine whether previous outputs of this task can be reused. + * The given closure is executed at task execution time. + * The closure is passed the task as a parameter. + * If the closure returns false, previous outputs of this task cannot be reused and the task will be executed. + * That means the task is out-of-date and no outputs will be loaded from the build cache. + *

+ * + *

+ * You can add multiple such predicates. + * The task outputs cannot be reused when any predicate returns false. + *

* * @param upToDateClosure The closure to use to determine whether the task outputs are up-to-date. */ void upToDateWhen(Closure upToDateClosure); /** - *

Adds a predicate to determine whether the outputs of this task are up-to-date. The given spec is evaluated at - * task execution time. If the spec returns false, the task outputs are considered out-of-date and the task will be - * executed.

- * - *

You can add multiple such predicates. The task outputs are considered out-of-date when any predicate returns - * false.

+ *

+ * Adds a predicate to determine whether previous outputs of this task can be reused. + * The given spec is evaluated at task execution time. + * If the spec returns false, previous outputs of this task cannot be reused and the task will be executed. + * That means the task is out-of-date and no outputs will be loaded from the build cache. + *

+ * + *

+ * You can add multiple such predicates. + * The task outputs cannot be reused when any predicate returns false. + *

* * @param upToDateSpec The spec to use to determine whether the task outputs are up-to-date. */ diff --git a/subprojects/core-api/src/main/java/org/gradle/api/tasks/incremental/IncrementalTaskInputs.java b/subprojects/core-api/src/main/java/org/gradle/api/tasks/incremental/IncrementalTaskInputs.java index e3243ac0a5a97..8987cb8d86877 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/tasks/incremental/IncrementalTaskInputs.java +++ b/subprojects/core-api/src/main/java/org/gradle/api/tasks/incremental/IncrementalTaskInputs.java @@ -36,9 +36,9 @@ * * {@literal @}TaskAction * void execute(IncrementalTaskInputs inputs) { - * if (!inputs.incremental) + * if (!inputs.incremental) { * project.delete(outputDir.listFiles()) - * + * } * inputs.outOfDate { change -> * def targetFile = project.file("$outputDir/${change.file.name}") * targetFile.text = change.file.text.reverse() @@ -73,7 +73,7 @@ @NonExtensible public interface IncrementalTaskInputs { /** - * Indicates if it was possible for Gradle to determine which exactly input files were out of date compared to a previous execution. + * Indicates if it was possible for Gradle to determine which input files were out of date compared to a previous execution. * This is not possible in the case of no previous execution, changed input properties, output files, etc. *

* When true: diff --git a/subprojects/core-api/src/main/java/org/gradle/caching/BuildCacheKey.java b/subprojects/core-api/src/main/java/org/gradle/caching/BuildCacheKey.java index 62d23b59f71de..ea1b560ed06b9 100644 --- a/subprojects/core-api/src/main/java/org/gradle/caching/BuildCacheKey.java +++ b/subprojects/core-api/src/main/java/org/gradle/caching/BuildCacheKey.java @@ -17,6 +17,7 @@ package org.gradle.caching; import org.gradle.api.Describable; +import org.gradle.api.Incubating; /** * Cache key identifying an entry in the build cache. @@ -28,4 +29,12 @@ public interface BuildCacheKey extends Describable { * Returns the string representation of the cache key. */ String getHashCode(); + + /** + * Returns the byte array representation of the cache key. + * + * @since 5.4 + */ + @Incubating + byte[] toByteArray(); } diff --git a/subprojects/core-api/src/main/java/org/gradle/caching/configuration/BuildCacheConfiguration.java b/subprojects/core-api/src/main/java/org/gradle/caching/configuration/BuildCacheConfiguration.java index a622f41e8e7f1..9dcf56961b395 100644 --- a/subprojects/core-api/src/main/java/org/gradle/caching/configuration/BuildCacheConfiguration.java +++ b/subprojects/core-api/src/main/java/org/gradle/caching/configuration/BuildCacheConfiguration.java @@ -46,6 +46,8 @@ public interface BuildCacheConfiguration { /** * Configures the local cache with the given type. * + *

Note: using any type except {@link org.gradle.caching.local.DirectoryBuildCache} is deprecated.

+ * *

If a local build cache has already been configured with a different type, this method replaces it.

*

Storing ("push") in the local build cache is enabled by default.

* @@ -56,6 +58,8 @@ public interface BuildCacheConfiguration { /** * Configures the local cache with the given type. * + *

Note: using any type except {@link org.gradle.caching.local.DirectoryBuildCache} is deprecated.

+ * *

If a local build cache has already been configured with a different type, this method replaces it.

*

If a local build cache has already been configured with the same type, this method configures it.

*

Storing ("push") in the local build cache is enabled by default.

diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/local/DirectoryBuildCache.java b/subprojects/core-api/src/main/java/org/gradle/caching/local/DirectoryBuildCache.java similarity index 100% rename from subprojects/build-cache/src/main/java/org/gradle/caching/local/DirectoryBuildCache.java rename to subprojects/core-api/src/main/java/org/gradle/caching/local/DirectoryBuildCache.java diff --git a/subprojects/build-cache/src/main/java/org/gradle/caching/local/package-info.java b/subprojects/core-api/src/main/java/org/gradle/caching/local/package-info.java similarity index 100% rename from subprojects/build-cache/src/main/java/org/gradle/caching/local/package-info.java rename to subprojects/core-api/src/main/java/org/gradle/caching/local/package-info.java diff --git a/subprojects/core-api/src/main/java/org/gradle/model/internal/type/ModelType.java b/subprojects/core-api/src/main/java/org/gradle/model/internal/type/ModelType.java index 17747b824a7ed..30c25877ebc40 100644 --- a/subprojects/core-api/src/main/java/org/gradle/model/internal/type/ModelType.java +++ b/subprojects/core-api/src/main/java/org/gradle/model/internal/type/ModelType.java @@ -18,7 +18,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.reflect.TypeToken; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.Cast; import javax.annotation.Nullable; diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/TaskExecution.java b/subprojects/core-api/src/main/java/org/gradle/work/ChangeType.java similarity index 68% rename from subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/TaskExecution.java rename to subprojects/core-api/src/main/java/org/gradle/work/ChangeType.java index dd86768f676d6..5082372681dff 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/TaskExecution.java +++ b/subprojects/core-api/src/main/java/org/gradle/work/ChangeType.java @@ -1,5 +1,5 @@ /* - * Copyright 2011 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.gradle.api.internal.changedetection.state; -import org.gradle.internal.execution.history.ExecutionState; +package org.gradle.work; + +import org.gradle.api.Incubating; /** - * The state for a single task execution. + * The type of change for e.g. an input file. + * + * @since 5.4 */ -public interface TaskExecution extends ExecutionState { - +@Incubating +public enum ChangeType { + ADDED, + MODIFIED, + REMOVED } diff --git a/subprojects/core-api/src/main/java/org/gradle/work/FileChange.java b/subprojects/core-api/src/main/java/org/gradle/work/FileChange.java new file mode 100644 index 0000000000000..b392bc9a3d6fc --- /dev/null +++ b/subprojects/core-api/src/main/java/org/gradle/work/FileChange.java @@ -0,0 +1,60 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.work; + +import org.gradle.api.Incubating; +import org.gradle.api.file.FileType; + +import java.io.File; + +/** + * A change to a file. + * + * @since 5.4 + */ +@Incubating +public interface FileChange { + + /** + * The file, which may no longer exist. + */ + File getFile(); + + /** + * The type of change to the file. + */ + ChangeType getChangeType(); + + /** + * The file type of the file. + * + *

+ * For {@link ChangeType#ADDED} and {@link ChangeType#MODIFIED}, the type of the file which was added/modified is reported. + * For {@link ChangeType#REMOVED} the type of the file which was removed is reported. + *

+ */ + FileType getFileType(); + + /** + * The normalized path of the file, as specified by the path normalization strategy. + * + *

+ * See {@link org.gradle.api.tasks.PathSensitivity}, {@link org.gradle.api.tasks.Classpath} and {@link org.gradle.api.tasks.CompileClasspath} for the different path normalization strategies. + *

+ */ + String getNormalizedPath(); +} diff --git a/subprojects/core-api/src/main/java/org/gradle/work/Incremental.java b/subprojects/core-api/src/main/java/org/gradle/work/Incremental.java new file mode 100644 index 0000000000000..c44b3688794cb --- /dev/null +++ b/subprojects/core-api/src/main/java/org/gradle/work/Incremental.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.work; + +import org.gradle.api.Incubating; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Track input changes for the annotated parameter. + * + *

+ * Inputs annotated with {@link Incremental} can be queried for changes via {@link InputChanges#getFileChanges(org.gradle.api.file.FileCollection)} or {@link org.gradle.work.InputChanges#getFileChanges(org.gradle.api.provider.Provider)}. + *

+ * + * @since 5.4 + */ +@Incubating +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD, ElementType.FIELD}) +@Documented +public @interface Incremental { +} diff --git a/subprojects/core-api/src/main/java/org/gradle/work/InputChanges.java b/subprojects/core-api/src/main/java/org/gradle/work/InputChanges.java new file mode 100644 index 0000000000000..900be5659607b --- /dev/null +++ b/subprojects/core-api/src/main/java/org/gradle/work/InputChanges.java @@ -0,0 +1,121 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.work; + +import org.gradle.api.Incubating; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.provider.Provider; + +/** + * Provides access to any input files that need to be processed by an incremental work action. + * + *

+ * An incremental work action is one that accepts a single {@link InputChanges} parameter. + * The work action can then query what changed for an input parameter since the last execution to only process the changes. + * + *

+ * abstract class IncrementalReverseTask extends DefaultTask {
+ *     {@literal @}InputDirectory
+ *     abstract DirectoryProperty getInputDir()
+ *
+ *     {@literal @}OutputDirectory
+ *     def File outputDir
+ *
+ *     {@literal @}TaskAction
+ *     void execute(InputChanges inputChanges) {
+ *         if (!inputChanges.incremental) {
+ *             project.delete(outputDir.listFiles())
+ *         }
+ *
+ *         inputChanges.getFileChanges(inputDir).each { change ->
+ *             if (change.changeType == ChangeType.REMOVED) {
+ *                 def targetFile = project.file("$outputDir/${change.file.name}")
+ *                 if (targetFile.exists()) {
+ *                     targetFile.delete()
+ *                 }
+ *             } else {
+ *                 def targetFile = project.file("$outputDir/${change.file.name}")
+ *                 targetFile.text = change.file.text.reverse()
+ *             }
+ *         }
+ *     }
+ * }
+ * 
+ * + *

+ * In the case where Gradle is unable to determine which input files need to be reprocessed, then all of the input files will be reported as {@link ChangeType#ADDED}. + * Cases where this occurs include: + *

    + *
  • There is no history available from a previous execution.
  • + *
  • A non-file input parameter has changed since the previous execution.
  • + *
  • One or more output files have changed since the previous execution.
  • + *
+ * + * @since 5.4 + */ +@Incubating +public interface InputChanges { + /** + * Indicates if it was possible for Gradle to determine which input files were out of date compared to a previous execution. + * Incremental inputs are unavailable when history is unavailable (i.e. this piece of work has never been executed before), or if there are changes to non-file input properties, or output files. + *

+ * When true: + *

+ *
    + *
  • {@link #getFileChanges(FileCollection)} and {@link #getFileChanges(Provider)} report changes to the input files compared to the previous execution.
  • + *
+ *

+ * When false: + *

+ *
    + *
  • Every input file is reported via {@link #getFileChanges(FileCollection)} and {@link #getFileChanges(Provider)} as if it was {@link ChangeType#ADDED}.
  • + *
+ */ + boolean isIncremental(); + + /** + * Changes for a parameter. + * + *

When {@link #isIncremental()} is {@code false}, then all elements of the parameter are returned as {@link ChangeType#ADDED}.

+ * + *

+ * Only input file properties annotated with {@literal @}{@link Incremental} or {@literal @}{@link org.gradle.api.tasks.SkipWhenEmpty} can be queried for changes. + *

+ * + * @param parameter The value of the parameter to query. + */ + Iterable getFileChanges(FileCollection parameter); + + /** + * Changes for a parameter. + * + *

When {@link #isIncremental()} is {@code false}, then all elements of the parameter are returned as {@link ChangeType#ADDED}.

+ * + *

+ * This method allows querying properties of type {@link org.gradle.api.file.RegularFileProperty} and {@link org.gradle.api.file.DirectoryProperty} for changes. + * These two types are typically used for {@literal @}{@link org.gradle.api.tasks.InputFile} and {@literal @}{@link org.gradle.api.tasks.InputDirectory} properties. + *

+ * + *

+ * Only input file properties annotated with {@literal @}{@link Incremental} or {@literal @}{@link org.gradle.api.tasks.SkipWhenEmpty} can be queried for changes. + *

+ * + * @param parameter The value of the parameter to query. + */ + Iterable getFileChanges(Provider parameter); +} diff --git a/subprojects/core-api/src/main/java/org/gradle/work/package-info.java b/subprojects/core-api/src/main/java/org/gradle/work/package-info.java new file mode 100644 index 0000000000000..e3171b5216419 --- /dev/null +++ b/subprojects/core-api/src/main/java/org/gradle/work/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Classes used for implementing units of work. + */ +@NonNullApi +package org.gradle.work; + +import org.gradle.api.NonNullApi; diff --git a/subprojects/core/core.gradle.kts b/subprojects/core/core.gradle.kts index d570d40ec541a..4f1693eceefbb 100755 --- a/subprojects/core/core.gradle.kts +++ b/subprojects/core/core.gradle.kts @@ -62,10 +62,8 @@ dependencies { implementation(library("asm")) implementation(library("asm_commons")) implementation(library("slf4j_api")) - implementation(library("commons_collections")) implementation(library("commons_io")) implementation(library("commons_lang")) - implementation(library("jcip")) implementation(library("nativePlatform")) implementation(library("commons_compress")) implementation(library("xmlApis")) diff --git a/subprojects/core/src/integTest/groovy/org/gradle/JansiEndUserIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/JansiEndUserIntegrationTest.groovy index 024de193f0297..4f72007c52f4f 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/JansiEndUserIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/JansiEndUserIntegrationTest.groovy @@ -188,7 +188,7 @@ class JansiEndUserIntegrationTest extends AbstractIntegrationSpec { static String annotationProcessorDependency(File repoDir, String processorDependency) { """ - sourceCompatibility = '1.6' + sourceCompatibility = '1.7' repositories { maven { @@ -249,7 +249,7 @@ class JansiEndUserIntegrationTest extends AbstractIntegrationSpec { group = '$group' version = '$version' - sourceCompatibility = '1.6' + sourceCompatibility = '1.7' dependencies { compile 'org.fusesource.jansi:jansi:$JANSI_VERSION' @@ -284,7 +284,7 @@ class JansiEndUserIntegrationTest extends AbstractIntegrationSpec { import org.fusesource.jansi.AnsiConsole; @SupportedAnnotationTypes({"org.gradle.Custom"}) - @SupportedSourceVersion(SourceVersion.RELEASE_6) + @SupportedSourceVersion(SourceVersion.RELEASE_7) public class MyProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnv) { diff --git a/subprojects/core/src/integTest/groovy/org/gradle/NativeServicesIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/NativeServicesIntegrationTest.groovy index e2eeca1cb9d7b..41bac1f3f454e 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/NativeServicesIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/NativeServicesIntegrationTest.groovy @@ -17,9 +17,11 @@ package org.gradle import org.gradle.integtests.fixtures.AbstractIntegrationSpec +import org.gradle.integtests.fixtures.executer.GradleContextualExecuter import org.gradle.internal.nativeintegration.jansi.JansiStorageLocator import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider import org.junit.Rule +import spock.lang.IgnoreIf import spock.lang.Issue class NativeServicesIntegrationTest extends AbstractIntegrationSpec { @@ -50,6 +52,7 @@ class NativeServicesIntegrationTest extends AbstractIntegrationSpec { } @Issue("GRADLE-3573") + @IgnoreIf({ GradleContextualExecuter.embedded }) def "jansi library is unpacked to gradle user home dir and isn't overwritten if existing"() { String tmpDirJvmOpt = "-Djava.io.tmpdir=$tmpDir.testDirectory.absolutePath" executer.withBuildJvmOpts(tmpDirJvmOpt) diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/ObjectExtensionInstantiationIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/ObjectExtensionInstantiationIntegrationTest.groovy index 4deb0e33a24fb..a4fb7e81aec4c 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/ObjectExtensionInstantiationIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/ObjectExtensionInstantiationIntegrationTest.groovy @@ -17,6 +17,7 @@ package org.gradle.api import org.gradle.integtests.fixtures.AbstractIntegrationSpec +import spock.lang.Unroll import javax.inject.Inject @@ -147,26 +148,36 @@ class ObjectExtensionInstantiationIntegrationTest extends AbstractIntegrationSpe failure.assertHasCause("Too many parameters provided for constructor for class Thing. Expected 2, received 3.") } - def "can create instance of interface with mutable property"() { + @Unroll + def "can create instance of interface with mutable property of type #type"() { buildFile << """ interface Thing { - String getValue() - void setValue(String value) + ${type} getValue() + void setValue(${type} value) } extensions.create("thing", Thing) - assert thing.value == null + assert thing.value == ${defaultValue} thing { - value = "123" + value = ${newValue} } - assert thing.value == "123" + assert thing.value == ${newValue} """ expect: succeeds() + + where: + type | defaultValue | newValue + "String" | null | "'123'" + "List" | null | "['a', 'b', 'c']" + "boolean" | false | true + "Boolean" | null | true + "int" | 0 | 12 + "Integer" | null | 12 } - def "can create instance of interface with read-only FileCollection property"() { + def "can create instance of interface with read-only ConfigurableFileCollection property"() { buildFile << """ interface Thing { ConfigurableFileCollection getValue() @@ -182,6 +193,114 @@ class ObjectExtensionInstantiationIntegrationTest extends AbstractIntegrationSpe succeeds() } + def "can create instance of interface with read-only Property property"() { + buildFile << """ + interface Thing { + Property getValue() + } + + extensions.create("thing", Thing) + assert thing.value.getOrNull() == null + thing { + value = "value" + } + assert thing.value.get() == "value" + """ + + expect: + succeeds() + } + + def "can create instance of interface with read-only RegularFileProperty property"() { + buildFile << """ + interface Thing { + RegularFileProperty getValue() + } + + extensions.create("thing", Thing) + assert thing.value.getOrNull() == null + thing { + value = file("thing.txt") + } + assert thing.value.get() == layout.projectDir.file("thing.txt") + """ + + expect: + succeeds() + } + + def "can create instance of interface with read-only DirectoryProperty property"() { + buildFile << """ + interface Thing { + DirectoryProperty getValue() + } + + extensions.create("thing", Thing) + assert thing.value.getOrNull() == null + thing { + value = file("thing.txt") + } + assert thing.value.get() == layout.projectDir.dir("thing.txt") + """ + + expect: + succeeds() + } + + def "can create instance of interface with read-only ListProperty property"() { + buildFile << """ + interface Thing { + ListProperty getValue() + } + + extensions.create("thing", Thing) + assert thing.value.getOrNull() == [] + thing { + value = ["thing"] + } + assert thing.value.get() == ["thing"] + """ + + expect: + succeeds() + } + + def "can create instance of interface with read-only SetProperty property"() { + buildFile << """ + interface Thing { + SetProperty getValue() + } + + extensions.create("thing", Thing) + assert thing.value.getOrNull() == [] as Set + thing { + value = ["thing"] + } + assert thing.value.get() == ["thing"] as Set + """ + + expect: + succeeds() + } + + def "can create instance of interface with read-only MapProperty property"() { + buildFile << """ + interface Thing { + MapProperty getValue() + } + + extensions.create("thing", Thing) + assert thing.value.getOrNull() == [:] + thing { + value = [a: "b"] + } + assert thing.value.get() == [a: "b"] + """ + + expect: + succeeds() + } + def "can create instance of abstract class with mutable property"() { buildFile << """ abstract class Thing { diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptExecutionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptExecutionIntegrationTest.groovy index 4b5875ca60f42..7e6360faaa42c 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptExecutionIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/SettingsScriptExecutionIntegrationTest.groovy @@ -18,7 +18,9 @@ package org.gradle.api import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.FeaturePreviewsFixture import org.gradle.integtests.fixtures.executer.ArtifactBuilder +import org.gradle.internal.os.OperatingSystem import org.gradle.test.fixtures.file.TestFile +import spock.lang.Issue import spock.lang.Unroll class SettingsScriptExecutionIntegrationTest extends AbstractIntegrationSpec { @@ -42,6 +44,46 @@ class SettingsScriptExecutionIntegrationTest extends AbstractIntegrationSpec { feature << FeaturePreviewsFixture.inactiveFeatures() } + @Issue("https://github.com/gradle/gradle/issues/8840") + def "can use exec in settings"() { + addExecToScript(settingsFile) + when: + succeeds() + then: + outputContains("hello from settings") + } + + @Issue("https://github.com/gradle/gradle/issues/8840") + def "can use exec in settings applied from another script"() { + settingsFile << """ + apply from: 'other.gradle' + """ + addExecToScript(file("other.gradle")) + when: + succeeds() + then: + outputContains("hello from settings") + } + + private void addExecToScript(TestFile scriptFile) { + file("message") << """ + hello from settings + """ + if (OperatingSystem.current().windows) { + scriptFile << """ + exec { + commandLine "cmd", "/c", "type", "message" + } + """ + } else { + scriptFile << """ + exec { + commandLine "cat", "message" + } + """ + } + } + def "notices changes to settings scripts that do not change the file length"() { settingsFile.text = "println 'counter: __'" long before = settingsFile.length() diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/file/FilePropertiesIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/file/FilePropertiesIntegrationTest.groovy index f8297bd42cbac..91cf39d7b17fa 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/file/FilePropertiesIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/file/FilePropertiesIntegrationTest.groovy @@ -100,6 +100,17 @@ assert custom.prop.get().asFile == file("build/dir3") custom.prop = file("dir4") assert custom.prop.get().asFile == file("dir4") +custom.prop.set((Directory)null) +assert custom.prop.getOrNull() == null + +custom.prop = file("foo") +custom.prop.set(null) +assert custom.prop.getOrNull() == null + +custom.prop = file("foo") +custom.prop.set((File)null) +assert custom.prop.getOrNull() == null + """ expect: @@ -131,6 +142,16 @@ assert custom.prop.get().asFile == file("build/file3") custom.prop = file("file4") assert custom.prop.get().asFile == file("file4") +custom.prop.set((RegularFile)null) +assert custom.prop.getOrNull() == null + +custom.prop = file("foo") +custom.prop.set(null) +assert custom.prop.getOrNull() == null + +custom.prop = file("foo") +custom.prop.set((File)null) +assert custom.prop.getOrNull() == null """ expect: diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/changedetection/CorruptedTaskHistoryIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/changedetection/CorruptedTaskHistoryIntegrationTest.groovy deleted file mode 100644 index f0ebb6d8e89d2..0000000000000 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/changedetection/CorruptedTaskHistoryIntegrationTest.groovy +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.changedetection - -import org.gradle.integtests.fixtures.AbstractIntegrationSpec -import spock.lang.Issue - -class CorruptedTaskHistoryIntegrationTest extends AbstractIntegrationSpec { - - // If this test starts to be flaky it points to a problem in the code! - // See the linked issue. - @Issue("https://github.com/gradle/gradle/issues/2827") - def "broken build doesn't corrupt the artifact history"() { - def numberOfFiles = 10 - def numberOfOutputFilesPerTask = numberOfFiles - def numberOfInputProperties = 10 - def numberOfTasks = 100 - def totalNumberOfOutputDirectories = numberOfTasks - def killPollInterval = 10 - def totalNumberOfOutputFiles = numberOfTasks * numberOfOutputFilesPerTask + totalNumberOfOutputDirectories - - setupTestProject(numberOfFiles, numberOfInputProperties, numberOfTasks, killPollInterval) - - executer.beforeExecute { - // We need a separate JVM in order not to kill the test JVM - requireGradleDistribution() - } - - when: - succeeds("createFiles") - succeeds("clean") - fails("createFiles", "-PkillMe=true", "--max-workers=${numberOfTasks}") - def createdFiles = file('build').allDescendants().size() as BigDecimal - println "\nNumber of created files when the process has been killed: ${createdFiles}" - - then: - createdFiles in ((0.1 * totalNumberOfOutputFiles)..(0.9 * totalNumberOfOutputFiles)) - - expect: - succeeds "createFiles" - } - - /** - * Setup the test project.
- * - * The test project is setup in a way that the Gradle build can be killed while it is writing to the task history repository, possibly corrupting it.
- * - * We create {@code numberOfTasks} tasks, called {@code createFiles${number}}. - * Each of those tasks has {@code numberOfInputProperties} directory inputs, each one of them pointing to the input directory {@code 'inputs'}. - * The {@code 'inputs'} directory contains {@code numberOfFiles}. - * The {@code createFiles${number}} tasks create {@code numberOfFiles} files into the output directory {@code 'build/output${number}'} by using the worker API. So the task actions execute in parallel. - * If the Gradle property {@code 'killMe'} is set to some truthy value, we start a {@link Thread} which checks every {@code killPollInterval} ms if there are more than 40 output directories in 'build` and kills the Gradle process if there are. - * Finally, there is one task depending on all the tasks which are creating files. This one is just called {@code createFiles}. - */ - private void setupTestProject(int numberOfFiles, int numberOfInputProperties, int numberOfTasks, int killPollInterval) { - buildFile << """ -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.InputDirectory -import org.gradle.api.tasks.OutputDirectory -import org.gradle.workers.IsolationMode -import org.gradle.workers.WorkerExecutor - -import javax.inject.Inject - -class CreateFiles implements Runnable { - - private final File outputDir - - @Inject - CreateFiles(File outputDir) { - this.outputDir=outputDir - } - - @Override - void run() { - (1..$numberOfFiles).each { - new File(outputDir, "\${it}.txt").text = "output\${it}" - } - } -} - -class CreateFilesTask extends DefaultTask { - private final WorkerExecutor workerExecutor - - @Inject - CreateFilesTask(WorkerExecutor workerExecutor) { - this.workerExecutor=workerExecutor - } - - ${(1..numberOfInputProperties).collect { inputProperty(it) }.join("\n")} - - @OutputDirectory - File outputDir - - @TaskAction - void createFiles() { - workerExecutor.submit(CreateFiles) { - isolationMode = IsolationMode.NONE - params(outputDir) - } - } -} - -apply plugin: 'base' - -task createFiles - -(1..$numberOfTasks).each { num -> - createFiles.dependsOn(tasks.create("createFiles\${num}", CreateFilesTask) { - ${(1..numberOfInputProperties).collect { "inputDir$it = file('inputs')" }.join("\n")} - outputDir = file("build/output\${num}") - }) -} - -if (project.findProperty("killMe")) { - new Thread({ - while (true) { - Thread.sleep(${killPollInterval}) - if (buildDir.exists() && buildDir.listFiles().size() > 20) { - System.exit(1) - } - } - }).start() -} - """ - - file('inputs').create { - (1..numberOfFiles).each { - file("input${it}.txt").text = "input${it}" - } - } - } - - private static String inputProperty(Integer postfix) { - """ - @InputDirectory - File inputDir${postfix} - """ - } -} diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/project/taskfactory/TaskPropertyNamingIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/project/taskfactory/TaskPropertyNamingIntegrationTest.groovy index 0577bc408996d..b2d4e7b9e440e 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/project/taskfactory/TaskPropertyNamingIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/project/taskfactory/TaskPropertyNamingIntegrationTest.groovy @@ -81,7 +81,7 @@ class TaskPropertyNamingIntegrationTest extends AbstractIntegrationSpec { def inputFiles = [:] TaskPropertyUtils.visitProperties(project.services.get(PropertyWalker), it, new PropertyVisitor.Adapter() { @Override - void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { inputFiles[propertyName] = project.files(value) } @@ -398,7 +398,7 @@ class TaskPropertyNamingIntegrationTest extends AbstractIntegrationSpec { } @Override - void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { println "Input file property '\${propertyName}'" } diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/SnapshotTaskInputsOperationIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/SnapshotTaskInputsOperationIntegrationTest.groovy index f8161d88220d5..4f118f74d025e 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/SnapshotTaskInputsOperationIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/SnapshotTaskInputsOperationIntegrationTest.groovy @@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks import org.gradle.api.Action import org.gradle.api.DefaultTask +import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationType import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.Nested @@ -26,7 +27,6 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.BuildOperationsFixture -import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationType import org.gradle.plugin.management.internal.autoapply.AutoAppliedBuildScanPlugin import spock.lang.Unroll @@ -263,7 +263,7 @@ class SnapshotTaskInputsOperationIntegrationTest extends AbstractIntegrationSpec with(aCompileJava.source) { hash != null - normalization == "NAME_ONLY" + normalization == "RELATIVE_PATH" roots.size() == 1 with(roots[0]) { path == file("a/src/main/java").absolutePath diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/TaskCacheabilityReasonIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/TaskCacheabilityReasonIntegrationTest.groovy index 07f8571b0c1be..c919792b2ddcf 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/TaskCacheabilityReasonIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/TaskCacheabilityReasonIntegrationTest.groovy @@ -16,131 +16,146 @@ package org.gradle.api.internal.tasks +import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationType import org.gradle.api.tasks.OutputDirectories import org.gradle.api.tasks.OutputFiles import org.gradle.integtests.fixtures.AbstractIntegrationSpec +import org.gradle.integtests.fixtures.BuildOperationsFixture import org.gradle.integtests.fixtures.DirectoryBuildCacheFixture import spock.lang.Unroll +import javax.annotation.Nullable + +import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.BUILD_CACHE_DISABLED +import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.CACHE_IF_SPEC_NOT_SATISFIED +import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.DO_NOT_CACHE_IF_SPEC_SATISFIED +import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TREE_OUTPUT +import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.NOT_ENABLED_FOR_TASK +import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.NO_OUTPUTS_DECLARED +import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.OVERLAPPING_OUTPUTS +import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.UNKNOWN + class TaskCacheabilityReasonIntegrationTest extends AbstractIntegrationSpec implements DirectoryBuildCacheFixture { + def operations = new BuildOperationsFixture(executer, testDirectoryProvider) + def setup() { buildFile << """ import org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory - - gradle.addListener(new TaskExecutionAdapter() { - void afterExecute(Task task, TaskState state) { - def taskOutputCaching = state.taskOutputCaching - assert taskOutputCaching.enabled == task.cachingEnabled - assert taskOutputCaching.disabledReason == task.disabledReason - assert taskOutputCaching.disabledReasonCategory == task.disabledReasonCategory - } - }) - class BaseTask extends DefaultTask { - // these are not inputs, they're used for verification - boolean cachingEnabled - String disabledReason - TaskOutputCachingDisabledReasonCategory disabledReasonCategory - } - - class NotCacheable extends BaseTask { + class NotCacheable extends DefaultTask { @Input String message = "Hello World" @OutputFile File outputFile = new File(temporaryDir, "output.txt") - + @TaskAction public void generate() { outputFile.text = message } } - + @CacheableTask - class Cacheable extends NotCacheable { + class Cacheable extends NotCacheable {} + + class NoOutputs extends DefaultTask { + @TaskAction + void generate() {} } + """ } def "default cacheability is BUILD_CACHE_DISABLED"() { buildFile << """ - task cacheable(type: Cacheable) { - cachingEnabled = false - disabledReason = "Task output caching is disabled" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.BUILD_CACHE_DISABLED - } - task notcacheable(type: NotCacheable) { - cachingEnabled = false - disabledReason = "Task output caching is disabled" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.BUILD_CACHE_DISABLED - } + task cacheable(type: Cacheable) {} + task notcacheable(type: NotCacheable) {} + task noOutputs(type: NoOutputs) {} """ - expect: - succeeds "cacheable", "notcacheable" + when: + run "cacheable" + then: + assertCachingDisabledFor BUILD_CACHE_DISABLED, "Build cache is disabled" + + when: + run "notcacheable" + then: + assertCachingDisabledFor BUILD_CACHE_DISABLED, "Build cache is disabled" + + when: + run "noOutputs" + then: + assertCachingDisabledFor BUILD_CACHE_DISABLED, "Build cache is disabled" } def "cacheability for non-cacheable task is NOT_ENABLED_FOR_TASK"() { buildFile << """ - task cacheable(type: NotCacheable) { - cachingEnabled = false - disabledReason = "Caching has not been enabled for the task" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.NOT_ENABLED_FOR_TASK - } + task notcacheable(type: NotCacheable) {} """ - expect: - withBuildCache().run "cacheable" + when: + withBuildCache().run "notcacheable" + then: + assertCachingDisabledFor NOT_ENABLED_FOR_TASK, "Caching has not been enabled for the task" } def "cacheability for a cacheable task is null"() { buildFile << """ - task cacheable(type: Cacheable) { - cachingEnabled = true - disabledReason = null - disabledReasonCategory = null - } + task cacheable(type: Cacheable) {} """ - expect: + when: withBuildCache().run "cacheable" + then: + assertCachingDisabledFor null, null } - def "cacheability for a task with no outputs is NO_OUTPUTS_DECLARED"() { + def "cacheability for a cacheable task with no outputs is NO_OUTPUTS_DECLARED"() { buildFile << """ @CacheableTask - class NoOutputs extends BaseTask { + class CacheableNoOutputs extends DefaultTask { @TaskAction void generate() {} } - task cacheable(type: NoOutputs) { - cachingEnabled = false - disabledReason = "No outputs declared" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.NO_OUTPUTS_DECLARED - } + task noOutputs(type: CacheableNoOutputs) {} """ - expect: - withBuildCache().run "cacheable" + when: + withBuildCache().run "noOutputs" + then: + assertCachingDisabledFor NO_OUTPUTS_DECLARED, "No outputs declared" } - def "cacheability for a task with no actions is UNKNOWN"() { + def "cacheability for a non-cacheable task with no outputs is NOT_ENABLED_FOR_TASK"() { buildFile << """ - @CacheableTask - class NoActions extends BaseTask { - } - - task cacheable(type: NoActions) { - cachingEnabled = false - disabledReason = "Cacheability was not determined" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.UNKNOWN + task noOutputs(type: NoOutputs) {} + """ + when: + withBuildCache().run "noOutputs" + then: + assertCachingDisabledFor NOT_ENABLED_FOR_TASK, "Caching has not been enabled for the task" + } + + @Unroll + def "cacheability for a task with no actions is UNKNOWN (cacheable: #cacheable)"() { + buildFile << """ + class NoActions extends DefaultTask {} + + task noActions { + outputs.cacheIf { $cacheable } } """ - expect: - withBuildCache().run "cacheable" + when: + withBuildCache().run "noActions" + then: + assertCachingDisabledFor UNKNOWN, "Cacheability was not determined" + + where: + cacheable << [true, false] } @Unroll def "cacheability for a task with @#annotation file tree outputs is NON_CACHEABLE_TREE_OUTPUT"() { buildFile << """ @CacheableTask - class PluralOutputs extends BaseTask { + class PluralOutputs extends DefaultTask { @$annotation def outputFiles = [project.fileTree('build/some-dir')] @@ -151,14 +166,12 @@ class TaskCacheabilityReasonIntegrationTest extends AbstractIntegrationSpec impl } } - task cacheable(type: PluralOutputs) { - cachingEnabled = false - disabledReason = "Output property 'outputFiles' contains a file tree" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TREE_OUTPUT - } + task pluralOutputs(type: PluralOutputs) """ - expect: - withBuildCache().run "cacheable" + when: + withBuildCache().run "pluralOutputs" + then: + assertCachingDisabledFor NON_CACHEABLE_TREE_OUTPUT, "Output property 'outputFiles' contains a file tree" where: annotation << [OutputFiles.simpleName, OutputDirectories.simpleName] @@ -166,60 +179,52 @@ class TaskCacheabilityReasonIntegrationTest extends AbstractIntegrationSpec impl def "cacheability for a task with overlapping outputs is OVERLAPPING_OUTPUTS"() { buildFile << """ - task cacheable(type: Cacheable) { - cachingEnabled = true - disabledReason = null - disabledReasonCategory = null - } - + task cacheable(type: Cacheable) task cacheableWithOverlap(type: Cacheable) { outputFile = cacheable.outputFile - cachingEnabled = false - disabledReason = "Gradle does not know how file '" + project.relativePath(outputFile) + "' was created (output property 'outputFile'). Task output caching requires exclusive access to output paths to guarantee correctness." - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.OVERLAPPING_OUTPUTS } """ - expect: + when: withBuildCache().run "cacheable", "cacheableWithOverlap" + then: + assertCachingDisabledFor null, null, ":cacheable" + assertCachingDisabledFor OVERLAPPING_OUTPUTS, "Gradle does not know how file 'build${File.separator}tmp${File.separator}cacheable${File.separator}output.txt' was created (output property 'outputFile'). Task output caching requires exclusive access to output paths to guarantee correctness.", ":cacheableWithOverlap" } def "cacheability for a task with a cacheIf is CACHE_IF_SPEC_NOT_SATISFIED"() { buildFile << """ task cacheable(type: Cacheable) { outputs.cacheIf("always false") { false } - cachingEnabled = false - disabledReason = "'always false' not satisfied" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.CACHE_IF_SPEC_NOT_SATISFIED } """ - expect: + when: withBuildCache().run "cacheable" + then: + assertCachingDisabledFor CACHE_IF_SPEC_NOT_SATISFIED, "'always false' not satisfied" } def "cacheability for a task with a doNotCacheIf is DO_NOT_CACHE_IF_SPEC_SATISFIED"() { buildFile << """ task cacheable(type: Cacheable) { outputs.doNotCacheIf("always true") { true } - cachingEnabled = false - disabledReason = "'always true' satisfied" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.DO_NOT_CACHE_IF_SPEC_SATISFIED } """ - expect: + when: withBuildCache().run "cacheable" + then: + assertCachingDisabledFor DO_NOT_CACHE_IF_SPEC_SATISFIED, "'always true' satisfied" } def "cacheability for a task with onlyIf is UNKNOWN"() { buildFile << """ task cacheable(type: Cacheable) { onlyIf { false } - cachingEnabled = false - disabledReason = "Cacheability was not determined" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.UNKNOWN } """ - expect: + when: withBuildCache().run "cacheable" + then: + assertCachingDisabledFor UNKNOWN, "Cacheability was not determined" } def "cacheability for a task with no sources is UNKNOWN"() { @@ -231,71 +236,86 @@ class TaskCacheabilityReasonIntegrationTest extends AbstractIntegrationSpec impl FileCollection empty = project.layout.files() } - task cacheable(type: NoSources) { - cachingEnabled = false - disabledReason = "Cacheability was not determined" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.UNKNOWN - } + task cacheable(type: NoSources) """ - expect: + when: withBuildCache().run "cacheable" + then: + assertCachingDisabledFor UNKNOWN, "Cacheability was not determined" } def "cacheability for a cacheable task that's up-to-date"() { buildFile << """ - task cacheable(type: Cacheable) { - cachingEnabled = true - disabledReason = null - disabledReasonCategory = null - } + task cacheable(type: Cacheable) """ + when: withBuildCache().run "cacheable" - expect: + then: + executedAndNotSkipped(":cacheable") + assertCachingDisabledFor null, null + + when: withBuildCache().run "cacheable" + then: + skipped(":cacheable") + assertCachingDisabledFor null, null } def "cacheability for a non-cacheable task that's up-to-date"() { buildFile << """ - task cacheable(type: NotCacheable) { - cachingEnabled = false - disabledReason = "Caching has not been enabled for the task" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.NOT_ENABLED_FOR_TASK - } + task notcacheable(type: NotCacheable) """ - withBuildCache().run "cacheable" - expect: - withBuildCache().run "cacheable" + when: + withBuildCache().run "notcacheable" + then: + executedAndNotSkipped(":notcacheable") + assertCachingDisabledFor NOT_ENABLED_FOR_TASK, "Caching has not been enabled for the task" + + when: + withBuildCache().run "notcacheable" + then: + skipped(":notcacheable") + assertCachingDisabledFor NOT_ENABLED_FOR_TASK, "Caching has not been enabled for the task" } def "cacheability for a failing cacheable task is null"() { buildFile << """ task cacheable(type: Cacheable) { - cachingEnabled = true - disabledReason = null - disabledReasonCategory = null doLast { throw new GradleException("boom") } } """ - expect: + when: withBuildCache().fails "cacheable" failure.assertHasCause("boom") + then: + assertCachingDisabledFor null, null } def "cacheability for a failing non-cacheable task is NOT_ENABLED_FOR_TASK"() { buildFile << """ task cacheable(type: NotCacheable) { - cachingEnabled = false - disabledReason = "Caching has not been enabled for the task" - disabledReasonCategory = TaskOutputCachingDisabledReasonCategory.NOT_ENABLED_FOR_TASK doLast { throw new GradleException("boom") } } """ - expect: + when: withBuildCache().fails "cacheable" failure.assertHasCause("boom") + then: + assertCachingDisabledFor NOT_ENABLED_FOR_TASK, "Caching has not been enabled for the task" + } + + private void assertCachingDisabledFor(@Nullable TaskOutputCachingDisabledReasonCategory category, @Nullable String message, @Nullable String taskPath = null) { + operations.only(ExecuteTaskBuildOperationType, { + if (taskPath && taskPath != it.details.taskPath) { + return false + } + assert it.result.cachingDisabledReasonCategory == category?.name() + assert it.result.cachingDisabledReasonMessage == message + return true + }) } } diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationTypeIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationTypeIntegrationTest.groovy index ab887f5f2e54b..a0b158730e84c 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationTypeIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationTypeIntegrationTest.groovy @@ -44,7 +44,7 @@ class ExecuteTaskBuildOperationTypeIntegrationTest extends AbstractIntegrationSp op.result.skipMessage == "UP-TO-DATE" op.result.actionable == false op.result.originBuildInvocationId == null - op.result.upToDateMessages == null + op.result.upToDateMessages == [] } def "emits operation result for failed task execution"() { diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTasksIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/AbstractIncrementalTasksIntegrationTest.groovy similarity index 65% rename from subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTasksIntegrationTest.groovy rename to subprojects/core/src/integTest/groovy/org/gradle/api/tasks/AbstractIncrementalTasksIntegrationTest.groovy index cf635fb5a57ff..00898644e735b 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTasksIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/AbstractIncrementalTasksIntegrationTest.groovy @@ -17,11 +17,17 @@ package org.gradle.api.tasks import org.gradle.integtests.fixtures.AbstractIntegrationSpec -import spock.lang.Issue -import spock.lang.Unroll +import org.gradle.internal.change.ChangeTypeInternal -class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { - def "setup"() { +abstract class AbstractIncrementalTasksIntegrationTest extends AbstractIntegrationSpec { + + abstract String getTaskAction() + + abstract ChangeTypeInternal getRebuildChangeType(); + + abstract String getPrimaryInputAnnotation(); + + def setup() { setupTaskSources() buildFile << buildFileBase buildFile << """ @@ -39,45 +45,22 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { file('outputs/file2.txt') << "outputFile2" } - private void setupTaskSources() { - file("buildSrc/src/main/groovy/BaseIncrementalTask.groovy") << """ + void setupTaskSources(String inputDirAnnotation = primaryInputAnnotation) { + file("buildSrc/src/main/groovy/BaseIncrementalTask.groovy").text = """ import org.gradle.api.* + import org.gradle.api.file.* import org.gradle.api.plugins.* import org.gradle.api.tasks.* import org.gradle.api.tasks.incremental.* + import org.gradle.work.* - class BaseIncrementalTask extends DefaultTask { + abstract class BaseIncrementalTask extends DefaultTask { + ${inputDirAnnotation} @InputDirectory - def File inputDir + abstract DirectoryProperty getInputDir() @TaskAction - void execute(IncrementalTaskInputs inputs) { - assert !(inputs instanceof ExtensionAware) - - if (project.hasProperty('forceFail')) { - throw new RuntimeException('failed') - } - - incrementalExecution = inputs.incremental - - inputs.outOfDate { change -> - if (change.added) { - addedFiles << change.file - } else { - changedFiles << change.file - } - } - - inputs.removed { change -> - removedFiles << change.file - } - - if (!inputs.incremental) { - createOutputsNonIncremental() - } - - touchOutputs() - } + $taskAction def touchOutputs() { } @@ -86,18 +69,18 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { } def addedFiles = [] - def changedFiles = [] + def modifiedFiles = [] def removedFiles = [] def incrementalExecution } """ - file("buildSrc/src/main/groovy/IncrementalTask.groovy") << """ + file("buildSrc/src/main/groovy/IncrementalTask.groovy").text = """ import org.gradle.api.* import org.gradle.api.plugins.* import org.gradle.api.tasks.* import org.gradle.api.tasks.incremental.* - class IncrementalTask extends BaseIncrementalTask { + abstract class IncrementalTask extends BaseIncrementalTask { @Input def String prop @@ -125,7 +108,7 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { ext { incrementalExecution = true added = [] - changed = [] + modified = [] removed = [] } @@ -133,7 +116,7 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { doLast { assert incremental.incrementalExecution == project.ext.incrementalExecution assert incremental.addedFiles.collect({ it.name }).sort() == project.ext.added - assert incremental.changedFiles.collect({ it.name }).sort() == project.ext.changed + assert incremental.modifiedFiles.collect({ it.name }).sort() == project.ext.modified assert incremental.removedFiles.collect({ it.name }).sort() == project.ext.removed } } @@ -142,7 +125,7 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { def "incremental task is informed that all input files are 'out-of-date' when run for the first time"() { expect: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is skipped when run with no changes since last execution"() { @@ -164,7 +147,7 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { file('inputs/file1.txt') << "changed content" then: - executesWithIncrementalContext("ext.changed = ['file1.txt']") + executesIncrementally(modified: ['file1.txt']) } def "incremental task is informed of 'out-of-date' files when input file added"() { @@ -175,7 +158,7 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { file('inputs/file3.txt') << "file3 content" then: - executesWithIncrementalContext("ext.added = ['file3.txt']") + executesIncrementally(added: ['file3.txt']) } def "incremental task is informed of 'out-of-date' files when input file removed"() { @@ -186,7 +169,7 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { file('inputs/file2.txt').delete() then: - executesWithIncrementalContext("ext.removed = ['file2.txt']") + executesIncrementally(removed: ['file2.txt']) } def "incremental task is informed of 'out-of-date' files when all input files removed"() { @@ -199,7 +182,7 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { file('inputs/file2.txt').delete() then: - executesWithIncrementalContext("ext.removed = ['file0.txt', 'file1.txt', 'file2.txt']") + executesIncrementally(removed: ['file0.txt', 'file1.txt', 'file2.txt']) } def "incremental task is informed of 'out-of-date' files with added, removed and modified files"() { @@ -213,11 +196,11 @@ class IncrementalTasksIntegrationTest extends AbstractIntegrationSpec { file('inputs/file4.txt') << "new file 4" then: - executesWithIncrementalContext(""" -ext.changed = ['file1.txt'] -ext.removed = ['file2.txt'] -ext.added = ['file3.txt', 'file4.txt'] -""") + executesIncrementally( + modified: ['file1.txt'], + removed: ['file2.txt'], + added: ['file3.txt', 'file4.txt'] + ) } def "incremental task is informed of 'out-of-date' files when task has no declared outputs or properties"() { @@ -235,7 +218,7 @@ ext.added = ['file3.txt', 'file4.txt'] file('inputs/file3.txt') << "file3 content" then: - executesWithIncrementalContext("ext.added = ['file3.txt']") + executesIncrementally(added: ['file3.txt']) } def "incremental task is informed that all input files are 'out-of-date' when input property has changed"() { @@ -246,19 +229,7 @@ ext.added = ['file3.txt', 'file4.txt'] buildFile << "incremental.prop = 'changed'" then: - executesWithRebuildContext() - } - - def "incremental task is informed that all input files are 'out-of-date' when input file property has been added"() { - given: - file('new-input.txt').text = "new input file" - previousExecution() - - when: - buildFile << "incremental.inputs.file('new-input.txt')" - - then: - executesWithRebuildContext("ext.changed += ['new-input.txt']") + executesNonIncrementally() } def "incremental task is informed that all input files are 'out-of-date' when input file property has been removed"() { @@ -276,7 +247,7 @@ ext.added = ['file3.txt', 'file4.txt'] toBeRemovedInputFile.delete() then: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is informed that all input files are 'out-of-date' when task class has changed"() { @@ -286,14 +257,14 @@ ext.added = ['file3.txt', 'file4.txt'] when: buildFile.text = buildFileBase buildFile << """ - class IncrementalTask2 extends BaseIncrementalTask {} + abstract class IncrementalTask2 extends BaseIncrementalTask {} task incremental(type: IncrementalTask2) { inputDir = project.mkdir('inputs') } """ then: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is informed that all input files are 'out-of-date' when output directory is changed"() { @@ -304,7 +275,7 @@ ext.added = ['file3.txt', 'file4.txt'] buildFile << "incremental.outputDir = project.mkdir('new-outputs')" then: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is informed that all input files are 'out-of-date' when output file has changed"() { @@ -315,7 +286,7 @@ ext.added = ['file3.txt', 'file4.txt'] file("outputs/file1.txt") << "further change" then: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is informed that all input files are 'out-of-date' when output file has been removed"() { @@ -326,7 +297,7 @@ ext.added = ['file3.txt', 'file4.txt'] file("outputs/file1.txt").delete() then: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is informed that all input files are 'out-of-date' when all output files have been removed"() { @@ -337,7 +308,7 @@ ext.added = ['file3.txt', 'file4.txt'] file("outputs").deleteDir() then: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is informed that all input files are 'out-of-date' when Task.upToDate() is false"() { @@ -348,7 +319,7 @@ ext.added = ['file3.txt', 'file4.txt'] buildFile << "incremental.outputs.upToDateWhen { false }" then: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is informed that all input files are 'out-of-date' when gradle is executed with --rerun-tasks"() { @@ -359,7 +330,7 @@ ext.added = ['file3.txt', 'file4.txt'] executer.withArgument("--rerun-tasks") then: - executesWithRebuildContext() + executesNonIncrementally() } def "incremental task is informed of 'out-of-date' files since previous successful execution"() { @@ -373,51 +344,7 @@ ext.added = ['file3.txt', 'file4.txt'] failedExecution() then: - executesWithIncrementalContext("ext.changed = ['file1.txt']") - } - - @Unroll - @Issue("https://github.com/gradle/gradle/issues/4166") - def "file in input dir appears in task inputs for #inputAnnotation"() { - buildFile << """ - class MyTask extends DefaultTask { - @${inputAnnotation} - File input - @OutputFile - File output - - @TaskAction - void doStuff(IncrementalTaskInputs inputs) { - def out = [] - inputs.outOfDate { - out << file.name - } - assert out.contains('child') - output.text = out.join('\\n') - } - } - - task myTask(type: MyTask) { - input = mkdir(inputDir) - output = file("build/output.txt") - } - """ - String myTask = ':myTask' - - when: - file("inputDir1/child") << "inputFile1" - run myTask, '-PinputDir=inputDir1' - then: - executedAndNotSkipped(myTask) - - when: - file("inputDir2/child") << "inputFile2" - run myTask, '-PinputDir=inputDir2' - then: - executedAndNotSkipped(myTask) - - where: - inputAnnotation << [InputFiles.name, InputDirectory.name] + executesIncrementally(modified: ['file1.txt']) } /* @@ -438,17 +365,31 @@ ext.added = ['file3.txt', 'file4.txt'] executer.withArguments() } - def executesWithIncrementalContext(String fileChanges) { - buildFile << fileChanges - succeeds "incrementalCheck" + def executesIncrementally(Map changes) { + executesIncrementalTask(incremental: true, *:changes) + } + + def executesNonIncrementally(List rebuiltFiles = preexistingInputs) { + executesIncrementalTask( + incremental: false, + (rebuildChangeType.name().toLowerCase(Locale.US)): rebuiltFiles + ) } - def executesWithRebuildContext(String fileChanges = "") { + List preexistingInputs = ['file0.txt', 'file1.txt', 'file2.txt', 'inputs'] + + def executesIncrementalTask(Map options) { + boolean incremental = options.incremental != false + List added = options.added ?: [] + List modified = options.modified ?: [] + List removed = options.removed ?: [] + buildFile << """ - ext.changed = ['file0.txt', 'file1.txt', 'file2.txt', 'inputs'] - ext.incrementalExecution = false -""" - buildFile << fileChanges - succeeds "incrementalCheck" + ext.added = ${added.collect { "'${it}'"}} + ext.modified = ${modified.collect { "'${it}'"}} + ext.removed = ${removed.collect { "'${it}'"}} + ext.incrementalExecution = ${incremental} + """ + succeeds("incrementalCheck") } } diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CacheTaskArchiveErrorIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CacheTaskArchiveErrorIntegrationTest.groovy index 8c1f7515a8be5..3669ea33ceaa3 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CacheTaskArchiveErrorIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CacheTaskArchiveErrorIntegrationTest.groovy @@ -29,7 +29,7 @@ class CacheTaskArchiveErrorIntegrationTest extends AbstractIntegrationSpec { def remoteCache = new TestBuildCache(file("remote-cache")) def setup() { - executer.beforeExecute { it.withBuildCacheEnabled() } + executer.beforeExecute { withBuildCacheEnabled() } settingsFile << localCache.localCacheConfiguration() } @@ -54,7 +54,7 @@ class CacheTaskArchiveErrorIntegrationTest extends AbstractIntegrationSpec { then: executer.withStackTraceChecksDisabled() succeeds "customTask" - output =~ /Failed to store cache entry .+ for task ':customTask'/ + output =~ /Failed to store cache entry .+/ output =~ /Could not pack tree 'output'/ localCache.empty localCache.listCacheTempFiles().empty @@ -173,8 +173,7 @@ class CacheTaskArchiveErrorIntegrationTest extends AbstractIntegrationSpec { then: executer.withStackTraceChecksDisabled() succeeds("clean", "customTask") - output =~ /Cleaning task ':customTask' after failed load from cache/ - output =~ /Failed to load cache entry for task ':customTask', falling back to executing task/ + output =~ /Failed to load cache entry for task ':customTask', cleaning outputs and falling back to \(non-incremental\) execution/ output =~ /Build cache entry .+ from local build cache is invalid/ output =~ /java.io.EOFException: Unexpected end of ZLIB input stream/ @@ -225,6 +224,40 @@ class CacheTaskArchiveErrorIntegrationTest extends AbstractIntegrationSpec { succeeds("cacheable") } + def "corrupted cache disables incremental execution"() { + when: + buildFile << """ + @CacheableTask + class CustomTask extends DefaultTask { + @OutputDirectory File outputDir = new File(temporaryDir, 'output') + @TaskAction + void generate(IncrementalTaskInputs inputs) { + println "> Incremental: \${inputs.incremental}" + new File(outputDir, "output").text = "OK" + } + } + + task cacheable(type: CustomTask) + """ + succeeds("cacheable") + + then: + localCache.listCacheFiles().size() == 1 + + when: + cleanBuildDir() + + and: + executer.withStackTraceChecksDisabled() + corruptMetadata({ metadata -> metadata.text = "corrupt" }) + succeeds("cacheable") + + then: + output =~ /Cached result format error, corrupted origin metadata\./ + output =~ /> Incremental: false/ + localCache.listCacheFailedFiles().size() == 1 + } + @Unroll def "local state declared via #api API is destroyed when task fails to load from cache"() { def localStateFile = file("local-state.json") diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedCustomTaskExecutionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedCustomTaskExecutionIntegrationTest.groovy index 1f84ed7167b04..af7e389e6abbf 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedCustomTaskExecutionIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedCustomTaskExecutionIntegrationTest.groovy @@ -626,7 +626,9 @@ class CachedCustomTaskExecutionIntegrationTest extends AbstractIntegrationSpec i when: withBuildCache().run "customTask", "--info" then: - output.contains "Caching disabled for task ':customTask': Task class was loaded with an unknown classloader (class 'CustomTask_Decorated')." + output.contains "Caching disabled for task ':customTask' because:\n" + + " Implementation type was loaded with an unknown classloader (class 'CustomTask_Decorated').\n" + " Additional implementation type was loaded with an unknown classloader (class 'CustomTask_Decorated')." } def "task with custom action loaded with custom classloader is not cached"() { @@ -668,7 +670,8 @@ class CachedCustomTaskExecutionIntegrationTest extends AbstractIntegrationSpec i when: withBuildCache().run "customTask", "--info" then: - output.contains "Caching disabled for task ':customTask': Task action was loaded with an unknown classloader (class 'CustomTaskAction')." + output.contains "Caching disabled for task ':customTask' because:\n" + + " Additional implementation type was loaded with an unknown classloader (class 'CustomTaskAction')." } def "task stays up-to-date after loaded from cache"() { diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedTaskExecutionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedTaskExecutionIntegrationTest.groovy index 2f1c0378bf682..9811a03058e3c 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedTaskExecutionIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedTaskExecutionIntegrationTest.groovy @@ -24,6 +24,7 @@ import org.gradle.internal.jvm.Jvm import org.gradle.test.fixtures.file.TestFile import org.gradle.util.TextUtil import spock.lang.IgnoreIf +import spock.lang.Unroll class CachedTaskExecutionIntegrationTest extends AbstractIntegrationSpec implements DirectoryBuildCacheFixture { public static final String ORIGINAL_HELLO_WORLD = """ @@ -86,9 +87,13 @@ class CachedTaskExecutionIntegrationTest extends AbstractIntegrationSpec impleme skippedTasks.containsAll ":compileJava" } - def "cached tasks are executed with --rerun-tasks"() { + @Unroll + def "cached tasks are executed with #rerunMethod"() { expect: cacheDir.listFiles() as List == [] + buildFile << """ + tasks.withType(JavaCompile).configureEach { it.outputs.upToDateWhen { project.findProperty("upToDateWhenFalse") == null } } + """ when: withBuildCache().run "jar" @@ -102,7 +107,7 @@ class CachedTaskExecutionIntegrationTest extends AbstractIntegrationSpec impleme withBuildCache().run "clean" when: - withBuildCache().run "jar", "--rerun-tasks" + withBuildCache().run "jar", rerunMethod def updatedCacheContents = listCacheFiles() def updatedModificationTimes = updatedCacheContents*.lastModified() then: @@ -111,6 +116,9 @@ class CachedTaskExecutionIntegrationTest extends AbstractIntegrationSpec impleme originalModificationTimes.size().times { i -> assert originalModificationTimes[i] < updatedModificationTimes[i] } + + where: + rerunMethod << ["--rerun-tasks", "-PupToDateWhenFalse=true"] } def "task results don't get stored when pushing is disabled"() { @@ -405,15 +413,11 @@ class CachedTaskExecutionIntegrationTest extends AbstractIntegrationSpec impleme then: skippedTasks.empty - [ - "taskImplementation", - "actionImplementation", - "inputValuePropertyHash for 'options.fork'", - "inputFilePropertyHash for 'classpath'", - "outputPropertyName", - ].each { - assert output.contains("Appending ${it} to build cache key:") - } + output.contains("Appending implementation to build cache key:") + output.contains("Appending additional implementation to build cache key:") + output.contains("Appending input value fingerprint for 'options.fork'") + output.contains("Appending input file fingerprints for 'classpath'") + output.contains("Appending output property name to build cache key: destinationDir") output.contains("Build cache key for task ':compileJava' is ") } @@ -427,8 +431,9 @@ class CachedTaskExecutionIntegrationTest extends AbstractIntegrationSpec impleme then: skippedTasks.empty output.contains("Build cache key for task ':compileJava' is ") - !output.contains("Appending taskClass to build cache key:") - !output.contains("Appending inputPropertyHash for") + !output.contains("Appending implementation to build cache key:") + !output.contains("Appending input value fingerprint for") + !output.contains("Appending input file fingerprints for 'classpath'") } def "compileJava is not cached if forked executable is used"() { @@ -441,7 +446,8 @@ class CachedTaskExecutionIntegrationTest extends AbstractIntegrationSpec impleme withBuildCache().run "compileJava", "--info" then: skippedTasks.empty - output.contains "Caching disabled for task ':compileJava': 'Forking compiler via ForkOptions.executable' satisfied" + output.contains "Caching disabled for task ':compileJava' because:\n" + + " 'Forking compiler via ForkOptions.executable' satisfied" expect: succeeds "clean" diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedTaskIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedTaskIntegrationTest.groovy index 515cc0055532f..4c9b9e0228fc3 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedTaskIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CachedTaskIntegrationTest.groovy @@ -58,7 +58,6 @@ class CachedTaskIntegrationTest extends AbstractIntegrationSpec implements Direc outputs.file("out.txt") outputs.cacheIf { true } doLast { - assert state.taskOutputCaching.enabled project.file("out.txt") << "xxx" if (project.hasProperty("fail")) { throw new RuntimeException("Boo!") diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy index 71ef00673e76c..672b1958b1aaf 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/CopyTaskIntegrationSpec.groovy @@ -1281,21 +1281,21 @@ class CopyTaskIntegrationSpec extends AbstractIntegrationSpec { file("src.txt").createNewFile() buildFile << """ task copy(type: Copy) { + outputs.cacheIf { true } ${mutation} from "src.txt" into "destination" } - - task check { - dependsOn copy - doLast { - assert !copy.state.taskOutputCaching.enabled - } - } """ - expect: - succeeds "check" + withBuildCache().run "copy" + file("destination").deleteDir() + + when: + withBuildCache().run"copy" + + then: + skippedTasks.empty where: description | mutation diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/DeferredTaskDefinitionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/DeferredTaskDefinitionIntegrationTest.groovy index 0344c5df1d506..0751736ffe738 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/DeferredTaskDefinitionIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/DeferredTaskDefinitionIntegrationTest.groovy @@ -264,7 +264,7 @@ class DeferredTaskDefinitionIntegrationTest extends AbstractIntegrationSpec { gradle.buildFinished { assert configureCount == 1 - assert tasksAllCount == 13 // built in tasks + task1 + assert tasksAllCount == 14 // built in tasks + task1 } """ diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalInputsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalInputsIntegrationTest.groovy new file mode 100644 index 0000000000000..f6969b256f546 --- /dev/null +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalInputsIntegrationTest.groovy @@ -0,0 +1,457 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.tasks + +import org.gradle.internal.change.ChangeTypeInternal +import org.gradle.work.Incremental +import spock.lang.Issue +import spock.lang.Unroll + +class IncrementalInputsIntegrationTest extends AbstractIncrementalTasksIntegrationTest { + + String getTaskAction() { + """ + void execute(InputChanges inputChanges) { + assert !(inputChanges instanceof ExtensionAware) + + if (project.hasProperty('forceFail')) { + throw new RuntimeException('failed') + } + + incrementalExecution = inputChanges.incremental + + inputChanges.getFileChanges(inputDir).each { change -> + switch (change.changeType) { + case ChangeType.ADDED: + addedFiles << change.file + break + case ChangeType.MODIFIED: + modifiedFiles << change.file + break + case ChangeType.REMOVED: + removedFiles << change.file + break + default: + throw new IllegalStateException() + } + } + + if (!inputChanges.incremental) { + createOutputsNonIncremental() + } + + touchOutputs() + } + """ + } + + @Override + ChangeTypeInternal getRebuildChangeType() { + return ChangeTypeInternal.ADDED + } + + @Override + String getPrimaryInputAnnotation() { + return "@${Incremental.simpleName}" + } + + def "incremental task is executed non-incrementally when input file property has been added"() { + given: + file('new-input.txt').text = "new input file" + previousExecution() + + when: + buildFile << "incremental.inputs.file('new-input.txt')" + + then: + executesNonIncrementally() + } + + @Unroll + @Issue("https://github.com/gradle/gradle/issues/4166") + def "file in input dir appears in task inputs for #inputAnnotation"() { + buildFile << """ + abstract class MyTask extends DefaultTask { + @${inputAnnotation} + @Incremental + abstract DirectoryProperty getInput() + @OutputFile + File output + + @TaskAction + void doStuff(InputChanges changes) { + def changed = changes.getFileChanges(input)*.file*.name as List + assert changed.contains('child') + output.text = changed.join('\\n') + } + } + + task myTask(type: MyTask) { + input = mkdir(inputDir) + output = file("build/output.txt") + } + """ + String myTask = ':myTask' + + when: + file("inputDir1/child") << "inputFile1" + run myTask, '-PinputDir=inputDir1' + then: + executedAndNotSkipped(myTask) + + when: + file("inputDir2/child") << "inputFile2" + run myTask, '-PinputDir=inputDir2' + then: + executedAndNotSkipped(myTask) + + where: + inputAnnotation << [InputFiles.name, InputDirectory.name] + } + + def "cannot query non-incremental file input parameters"() { + given: + buildFile << """ + abstract class WithNonIncrementalInput extends BaseIncrementalTask { + + @InputFile + abstract RegularFileProperty getNonIncrementalInput() + + @Override + void execute(InputChanges inputChanges) { + inputChanges.getFileChanges(nonIncrementalInput) + } + } + + task withNonIncrementalInput(type: WithNonIncrementalInput) { + inputDir = file("inputs") + nonIncrementalInput = file("nonIncremental") + } + """ + file("nonIncremental").text = "input" + + expect: + fails("withNonIncrementalInput") + failure.assertHasCause("Cannot query incremental changes: No property found for value property(interface org.gradle.api.file.RegularFile, fixed(class org.gradle.api.internal.file.DefaultFilePropertyFactory\$FixedFile, ${file( "nonIncremental").absolutePath})). Incremental properties: inputDir.") + } + + def "changes to non-incremental input parameters cause a rebuild"() { + given: + buildFile << """ + abstract class WithNonIncrementalInput extends BaseIncrementalTask { + + @InputFile + File nonIncrementalInput + + @Override + void execute(InputChanges changes) { + super.execute(changes) + assert !changes.incremental + } + } + + task withNonIncrementalInput(type: WithNonIncrementalInput) { + inputDir = file("inputs") + nonIncrementalInput = file("nonIncremental") + } + """ + file("nonIncremental").text = "input" + run("withNonIncrementalInput") + + when: + file("inputs/new-input-file.txt") << "new file" + file("nonIncremental").text = 'changed' + then: + succeeds("withNonIncrementalInput") + } + + def "properties annotated with SkipWhenEmpty are incremental"() { + setupTaskSources("@${SkipWhenEmpty.simpleName}") + + given: + previousExecution() + + when: + file('inputs/file1.txt') << "changed content" + + then: + executesIncrementally(modified: ['file1.txt']) + } + + def "two incremental inputs cannot have the same value"() { + buildFile << """ + + class MyTask extends DefaultTask { + @Incremental + @InputDirectory + File inputOne + + @Incremental + @InputDirectory + File inputTwo + + @OutputDirectory + File outputDirectory + + @TaskAction + void run(InputChanges changes) { + new File(outputDirectory, "one.txt").text = changes.getFileChanges(inputOne)*.file*.name.join("\\n") + new File(outputDirectory, "two.txt").text = changes.getFileChanges(inputTwo)*.file*.name.join("\\n") + } + } + + task myTask(type: MyTask) { + inputOne = file("input") + inputTwo = file("input") + outputDirectory = file("build/output") + } + """ + + file("input").createDir() + + expect: + fails("myTask") + failureHasCause("Multiple entries with same key: ${file('input').absolutePath}=inputTwo and ${file('input').absolutePath}=inputOne") + } + + def "two incremental file properties can point to the same file"() { + buildFile << """ + abstract class MyTask extends DefaultTask { + @Incremental + @InputDirectory + abstract DirectoryProperty getInputOne() + + @Incremental + @InputDirectory + abstract DirectoryProperty getInputTwo() + + @OutputDirectory + File outputDirectory + + @TaskAction + void run(InputChanges changes) { + new File(outputDirectory, "one.txt").text = changes.getFileChanges(inputOne)*.file*.name.join("\\n") + new File(outputDirectory, "two.txt").text = changes.getFileChanges(inputTwo)*.file*.name.join("\\n") + } + } + + task myTask(type: MyTask) { + inputOne = file("input") + inputTwo = file("input") + outputDirectory = file("build/output") + } + """ + + file("input").createDir() + + expect: + succeeds("myTask") + } + + def "values are required for incremental inputs"() { + buildFile << """ + + abstract class MyTask extends DefaultTask { + @Incremental + @Optional + @InputDirectory + ${propertyDefinition} + + @OutputDirectory + File outputDirectory + + @TaskAction + void run(InputChanges changes) { + new File(outputDirectory, "output.txt").text = "Success" + } + } + + task myTask(type: MyTask) { + outputDirectory = file("build/output") + } + """ + + file("input").createDir() + + expect: + fails("myTask") + failure.assertHasDescription("Execution failed for task ':myTask'.") + failure.assertHasCause("Must specify a value for incremental input property 'input'.") + + where: + propertyDefinition << ["abstract DirectoryProperty getInput()", "abstract RegularFileProperty getInput()", "File input"] + } + + @Unroll + def "provides normalized paths (#pathSensitivity)"() { + buildFile << """ + abstract class MyCopy extends DefaultTask { + @Incremental + @PathSensitive(PathSensitivity.${pathSensitivity.name()}) + @InputDirectory + abstract DirectoryProperty getInputDirectory() + + @OutputDirectory + abstract DirectoryProperty getOutputDirectory() + + @TaskAction + void copy(InputChanges changes) { + if (!changes.incremental) { + org.gradle.util.GFileUtils.cleanDirectory(outputDirectory.get().asFile) + } + changes.getFileChanges(inputDirectory).each { change -> + File outputFile = new File(outputDirectory.get().asFile, change.normalizedPath) + if (change.changeType == ChangeType.REMOVED) { + outputFile.delete() + } else { + if (change.file.file) { + outputFile.parentFile.mkdirs() + outputFile.text = change.file.text + } + } + } + } + } + + task copy(type: MyCopy) { + inputDirectory = file("input") + outputDirectory = file("build/output") + } + """ + def toBeModifiedPath = "in/some/subdir/input1.txt" + def toBeRemovedPath = "in/some/subdir/input2.txt" + def toBeAddedPath = "in/some/other/subdir/other-input.txt" + file("input/$toBeModifiedPath").text = "input to copy" + file("input/${toBeRemovedPath}").text = "input to copy" + + when: + run("copy") + then: + executedAndNotSkipped(":copy") + file("build/output/${normalizedPaths.modified}").text == "input to copy" + file("build/output/${normalizedPaths.removed}").text == "input to copy" + + when: + file("input/${toBeAddedPath}").text = "other input" + file("input/${toBeModifiedPath}").text = "modified" + assert file("input/${toBeRemovedPath}").delete() + run("copy") + then: + executedAndNotSkipped(":copy") + file("build/output/${normalizedPaths.modified}").text == "modified" + !file("build/output/${normalizedPaths.removed}").exists() + file("build/output/${normalizedPaths.added}").text == "other input" + + where: + pathSensitivity | normalizedPaths + PathSensitivity.RELATIVE | [modified: "in/some/subdir/input1.txt", added: "in/some/other/subdir/other-input.txt", removed: "in/some/subdir/input2.txt"] + PathSensitivity.NAME_ONLY | [modified: "input1.txt", added: "other-input.txt", removed: "input2.txt"] + } + + @Unroll + def "provides the file type"() { + file("buildSrc").deleteDir() + buildFile.text = """ + abstract class MyCopy extends DefaultTask { + @Incremental + @PathSensitive(PathSensitivity.RELATIVE) + @InputFiles + abstract DirectoryProperty getInputDirectory() + + @OutputDirectory + abstract DirectoryProperty getOutputDirectory() + + @TaskAction + void copy(InputChanges changes) { + if (!changes.incremental) { + println("Full rebuild - cleaning output directory") + org.gradle.util.GFileUtils.cleanDirectory(outputDirectory.get().asFile) + } + changes.getFileChanges(inputDirectory).each { change -> + File outputFile = new File(outputDirectory.get().asFile, change.normalizedPath) + if (change.changeType == ChangeType.REMOVED) { + assert change.fileType == determineFileType(outputFile) + if (change.fileType == FileType.FILE) { + println "deleting \${outputFile}" + assert outputFile.delete() + } + } else { + assert change.fileType == determineFileType(change.file) + if (change.fileType == FileType.FILE) { + outputFile.parentFile.mkdirs() + outputFile.text = change.file.text + } + } + } + } + + protected FileType determineFileType(File file) { + if (file.file) { + return FileType.FILE + } + if (file.directory) { + return FileType.DIRECTORY + } + return FileType.MISSING + } + } + + task copy(type: MyCopy) { + inputDirectory = file("input") + outputDirectory = file("build/output") + } + """ + def inputDir = file("input") + def outputDir = file("build/output") + inputDir.file("modified.txt").text = "input to copy" + inputDir.file("subdir/removed.txt").text = "input to copy" + + when: + run("copy") + then: + executedAndNotSkipped(":copy") + outputDir.assertHasDescendants("modified.txt", "subdir/removed.txt") + + when: + inputDir.file("added.txt").text = "other input" + inputDir.file("modified.txt").text = "modified" + assert inputDir.file("subdir/removed.txt").delete() + assert inputDir.file("subdir").delete() + run("copy") + then: + executedAndNotSkipped(":copy") + outputDir.assertHasDescendants("modified.txt", "added.txt") + + when: + inputDir.forceDeleteDir() + run("copy") + then: + executedAndNotSkipped(":copy") + outputDir.assertIsEmptyDir() + + when: + inputDir.file("modified.txt").text = "some input" + run("copy") + // force rebuild + outputDir.file("modified.txt").text = "changed" + inputDir.forceDeleteDir() + run("copy") + then: + executedAndNotSkipped(":copy") + outputDir.assertIsEmptyDir() + } +} diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTaskInputsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTaskInputsIntegrationTest.groovy new file mode 100644 index 0000000000000..a9e1b3049013d --- /dev/null +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/IncrementalTaskInputsIntegrationTest.groovy @@ -0,0 +1,122 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.tasks + +import org.gradle.internal.change.ChangeTypeInternal +import spock.lang.Issue +import spock.lang.Unroll + +class IncrementalTaskInputsIntegrationTest extends AbstractIncrementalTasksIntegrationTest { + + String getTaskAction() { + """ + void execute(IncrementalTaskInputs inputs) { + assert !(inputs instanceof ExtensionAware) + + if (project.hasProperty('forceFail')) { + throw new RuntimeException('failed') + } + + incrementalExecution = inputs.incremental + + inputs.outOfDate { change -> + if (change.added) { + addedFiles << change.file + } else { + modifiedFiles << change.file + } + } + + inputs.removed { change -> + removedFiles << change.file + } + + if (!inputs.incremental) { + createOutputsNonIncremental() + } + + touchOutputs() + } + """ + } + + @Override + ChangeTypeInternal getRebuildChangeType() { + ChangeTypeInternal.MODIFIED + } + + @Override + String getPrimaryInputAnnotation() { + return "" + } + + def "incremental task is executed non-incrementally when input file property has been added"() { + given: + file('new-input.txt').text = "new input file" + previousExecution() + + when: + buildFile << "incremental.inputs.file('new-input.txt')" + + then: + executesNonIncrementally(preexistingInputs + ['new-input.txt']) + } + + @Unroll + @Issue("https://github.com/gradle/gradle/issues/4166") + def "file in input dir appears in task inputs for #inputAnnotation"() { + buildFile << """ + class MyTask extends DefaultTask { + @${inputAnnotation} + File input + @OutputFile + File output + + @TaskAction + void doStuff(IncrementalTaskInputs inputs) { + def out = [] + inputs.outOfDate { + out << file.name + } + assert out.contains('child') + output.text = out.join('\\n') + } + } + + task myTask(type: MyTask) { + input = mkdir(inputDir) + output = file("build/output.txt") + } + """ + String myTask = ':myTask' + + when: + file("inputDir1/child") << "inputFile1" + run myTask, '-PinputDir=inputDir1' + then: + executedAndNotSkipped(myTask) + + when: + file("inputDir2/child") << "inputFile2" + run myTask, '-PinputDir=inputDir2' + then: + executedAndNotSkipped(myTask) + + where: + inputAnnotation << [InputFiles.name, InputDirectory.name] + } +} diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/LambdaInputsIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/LambdaInputsIntegrationTest.groovy index 4413f9fc64f79..640e68d5b99ef 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/LambdaInputsIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/LambdaInputsIntegrationTest.groovy @@ -231,7 +231,7 @@ class LambdaInputsIntegrationTest extends AbstractIntegrationSpec implements Dir myTask.doLast(LambdaAction.ACTION) """ - def nonCacheableActionReason = 'Task action was implemented by the Java lambda \'LambdaAction$$Lambda$\'. Using Java lambdas is not supported, use an (anonymous) inner class instead.' + def nonCacheableActionReason = 'Additional implementation type was implemented by the Java lambda \'LambdaAction$$Lambda$\'. Using Java lambdas is not supported, use an (anonymous) inner class instead.' when: withBuildCache().run "myTask", "-info" @@ -247,7 +247,8 @@ class LambdaInputsIntegrationTest extends AbstractIntegrationSpec implements Dir } private void assertInvalidNonCacheableTask(String taskPath, String reason) { - assert sanitizedOutput.contains("Caching disabled for task '${taskPath}': ${reason}") + assert sanitizedOutput.contains("Caching disabled for task '${taskPath}' because:\n" + + " ${reason}") } private String getSanitizedOutput() { diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/NestedInputIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/NestedInputIntegrationTest.groovy index e0ec8c819ca4a..16df8f9e1a26a 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/NestedInputIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/NestedInputIntegrationTest.groovy @@ -760,7 +760,8 @@ class NestedInputIntegrationTest extends AbstractIntegrationSpec { when: withBuildCache().run "customTask", "--info", "-D${BuildCacheDebugLoggingOption.GRADLE_PROPERTY}=true" then: - output.contains "Caching disabled for task ':customTask': Non-cacheable inputs: property 'bean' was loaded with an unknown classloader (class 'NestedBean')." + output.contains "Caching disabled for task ':customTask' because:\n" + + " Non-cacheable inputs: property 'bean' was loaded with an unknown classloader (class 'NestedBean')." } def "task with nested bean loaded with custom classloader is never up-to-date"() { diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskDefinitionIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskDefinitionIntegrationTest.groovy index f17c9266c7f79..fcac5afbaccbf 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskDefinitionIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskDefinitionIntegrationTest.groovy @@ -522,7 +522,7 @@ class TaskDefinitionIntegrationTest extends AbstractIntegrationSpec { def schema = tasks.collectionSchema.elements.collectEntries { e -> [ e.name, e.publicType.simpleName ] } - assert schema.size() == 16 + assert schema.size() == 17 assert schema["help"] == "Help" @@ -540,6 +540,8 @@ class TaskDefinitionIntegrationTest extends AbstractIntegrationSpec { assert schema["init"] == "InitBuild" assert schema["wrapper"] == "Wrapper" + + assert schema["prepareKotlinBuildScriptModel"] == "DefaultTask" assert schema["foo"] == "Foo" assert schema["bar"] == "Foo" @@ -566,7 +568,7 @@ class TaskDefinitionIntegrationTest extends AbstractIntegrationSpec { failure.assertHasCause("Adding a task provider directly to the task container is not supported.") } - def "can define task using abstract FileCollection getter"() { + def "can define task with abstract ConfigurableFileCollection getter"() { given: buildFile << """ abstract class MyTask extends DefaultTask { diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskInputPropertiesIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskInputPropertiesIntegrationTest.groovy index 3ee3fae67e867..848264825492f 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskInputPropertiesIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskInputPropertiesIntegrationTest.groovy @@ -17,7 +17,11 @@ package org.gradle.api.tasks import org.gradle.api.file.FileCollection +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.MapProperty +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider +import org.gradle.api.provider.SetProperty import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.TestBuildCache import org.gradle.internal.Actions @@ -369,7 +373,7 @@ task someTask { } @Unroll - def "task can use property of type #type"() { + def "task can use input property of type #type"() { file("buildSrc/src/main/java/SomeTask.java") << """ import org.gradle.api.DefaultTask; import org.gradle.api.tasks.TaskAction; @@ -379,11 +383,12 @@ import org.gradle.api.tasks.Optional; import java.io.File; public class SomeTask extends DefaultTask { - public $type v; + private $type v; @Input public $type getV() { return v; } + void setV($type v) { this.v = v; } - public File d; + File d; @OutputDirectory public File getD() { return d; } @@ -423,29 +428,33 @@ task someTask(type: SomeTask) { skipped(":someTask") where: - type | initialValue | newValue - "String" | "'value 1'" | "'value 2'" - "java.io.File" | "file('file1')" | "file('file2')" - "boolean" | "true" | "false" - "Boolean" | "Boolean.TRUE" | "Boolean.FALSE" - "int" | "123" | "-45" - "Integer" | "123" | "-45" - "long" | "123" | "-45" - "Long" | "123" | "-45" - "short" | "123" | "-45" - "Short" | "123" | "-45" - "java.math.BigDecimal" | "12.3" | "-45.432" - "java.math.BigInteger" | "12" | "-45" - "java.util.List" | "['value1', 'value2']" | "['value1']" - "java.util.List" | "[]" | "['value1', null, false, 123, 12.4, ['abc'], [true] as Set]" - "String[]" | "new String[0]" | "['abc'] as String[]" - "Object[]" | "[123, 'abc'] as Object[]" | "['abc'] as String[]" - "java.util.Collection" | "['value1', 'value2']" | "['value1'] as SortedSet" - "java.util.Set" | "['value1', 'value2'] as Set" | "['value1'] as Set" - "Iterable" | "[file('1'), file('2')] as Set" | "files('1')" - FileCollection.name | "files('1', '2')" | "configurations.create('empty')" - "java.util.Map" | "[a: true, b: false]" | "[a: true, b: true]" - "${Provider.name}" | "providers.provider { 'a' }" | "providers.provider { 'b' }" + type | initialValue | newValue + "String" | "'value 1'" | "'value 2'" + "java.io.File" | "file('file1')" | "file('file2')" + "boolean" | "true" | "false" + "Boolean" | "Boolean.TRUE" | "Boolean.FALSE" + "int" | "123" | "-45" + "Integer" | "123" | "-45" + "long" | "123" | "-45" + "Long" | "123" | "-45" + "short" | "123" | "-45" + "Short" | "123" | "-45" + "java.math.BigDecimal" | "12.3" | "-45.432" + "java.math.BigInteger" | "12" | "-45" + "java.util.List" | "['value1', 'value2']" | "['value1']" + "java.util.List" | "[]" | "['value1', null, false, 123, 12.4, ['abc'], [true] as Set]" + "String[]" | "new String[0]" | "['abc'] as String[]" + "Object[]" | "[123, 'abc'] as Object[]" | "['abc'] as String[]" + "java.util.Collection" | "['value1', 'value2']" | "['value1'] as SortedSet" + "java.util.Set" | "['value1', 'value2'] as Set" | "['value1'] as Set" + "Iterable" | "[file('1'), file('2')] as Set" | "files('1')" + FileCollection.name | "files('1', '2')" | "configurations.create('empty')" + "java.util.Map" | "[a: true, b: false]" | "[a: true, b: true]" + "${Provider.name}" | "providers.provider { 'a' }" | "providers.provider { 'b' }" + "${Property.name}" | "objects.property(String); v.set('abc')" | "objects.property(String); v.set('123')" + "${ListProperty.name}" | "objects.listProperty(String); v.set(['abc'])" | "objects.listProperty(String); v.set(['123'])" + "${SetProperty.name}" | "objects.setProperty(String); v.set(['abc'])" | "objects.setProperty(String); v.set(['123'])" + "${MapProperty.name}" | "objects.mapProperty(String, Number); v.set([a: 12])" | "objects.mapProperty(String, Number); v.set([a: 10])" } def "null input properties registered via TaskInputs.property are not allowed"() { @@ -708,8 +717,8 @@ task someTask(type: SomeTask) { inputFile.text = "input" def expectedCounts = [inputFile: 3, outputFile: 3, nestedInput: 3, inputValue: 1, nestedInputValue: 1] def expectedUpToDateCounts = [inputFile: 2, outputFile: 2, nestedInput: 3, inputValue: 1, nestedInputValue: 1] - def arguments = ["assertInputCounts"] + expectedCounts.collect { name, count -> "-P${name}Count=${count}"} - def upToDateArguments = ["assertInputCounts"] + expectedUpToDateCounts.collect { name, count -> "-P${name}Count=${count}"} + def arguments = ["assertInputCounts"] + expectedCounts.collect { name, count -> "-P${name}Count=${count}" } + def upToDateArguments = ["assertInputCounts"] + expectedUpToDateCounts.collect { name, count -> "-P${name}Count=${count}" } def localCache = new TestBuildCache(file('cache-dir')) settingsFile << localCache.localCacheConfiguration() diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskTimeoutIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskTimeoutIntegrationTest.groovy index e7b88f6cb3046..8bca409b81f3a 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskTimeoutIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/TaskTimeoutIntegrationTest.groovy @@ -152,6 +152,11 @@ class TaskTimeoutIntegrationTest extends AbstractIntegrationSpec { @Unroll def "timeout stops long running work items with #isolationMode isolation"() { given: + if (isolationMode == IsolationMode.PROCESS) { + // worker starting threads can be interrupted during worker startup and cause a 'Could not initialise system classpath' exception. + // See: https://github.com/gradle/gradle/issues/8699 + executer.withStackTraceChecksDisabled() + } buildFile << """ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -193,6 +198,9 @@ class TaskTimeoutIntegrationTest extends AbstractIntegrationSpec { fails "block" failure.assertHasDescription("Execution failed for task ':block'.") failure.assertHasCause("Timeout has been exceeded") + if (isolationMode == IsolationMode.PROCESS && failure.output.contains("Caused by:")) { + assert failure.output.contains("Error occurred during initialization of VM") + } where: isolationMode << IsolationMode.values() diff --git a/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/console/AutoConsoleExecOutputIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/console/AutoConsoleExecOutputIntegrationTest.groovy new file mode 100644 index 0000000000000..36a3a59928e41 --- /dev/null +++ b/subprojects/core/src/integTest/groovy/org/gradle/api/tasks/console/AutoConsoleExecOutputIntegrationTest.groovy @@ -0,0 +1,23 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.tasks.console + +import org.gradle.api.logging.configuration.ConsoleOutput + +class AutoConsoleExecOutputIntegrationTest extends AbstractExecOutputIntegrationTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/core/src/integTest/groovy/org/gradle/execution/taskgraph/RuleTaskBridgingIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/execution/taskgraph/RuleTaskBridgingIntegrationTest.groovy index 43f5de593640d..9e374bc21075c 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/execution/taskgraph/RuleTaskBridgingIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/execution/taskgraph/RuleTaskBridgingIntegrationTest.groovy @@ -51,7 +51,7 @@ class RuleTaskBridgingIntegrationTest extends AbstractIntegrationSpec implements then: output.contains "as map: ModelMap 'tasks'" - output.contains "as container: [task ':buildEnvironment', task ':components', task ':dependencies', task ':dependencyInsight', task ':dependentComponents', task ':help', task ':init', task ':model', task ':projects', task ':properties', task ':tasks', task ':wrapper']" + output.contains "as container: [task ':buildEnvironment', task ':components', task ':dependencies', task ':dependencyInsight', task ':dependentComponents', task ':help', task ':init', task ':model', task ':prepareKotlinBuildScriptModel', task ':projects', task ':properties', task ':tasks', task ':wrapper']" output.contains "as model element: ModelMap 'tasks'" output.contains "name: tasks" } diff --git a/subprojects/core/src/integTest/groovy/org/gradle/internal/operations/logging/LoggingBuildOperationProgressIntegTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/internal/operations/logging/LoggingBuildOperationProgressIntegTest.groovy index 0bef423163a3a..ad29399bad3a4 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/internal/operations/logging/LoggingBuildOperationProgressIntegTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/internal/operations/logging/LoggingBuildOperationProgressIntegTest.groovy @@ -19,6 +19,7 @@ package org.gradle.internal.operations.logging import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationType import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.BuildOperationsFixture +import org.gradle.internal.logging.events.LogEvent import org.gradle.internal.logging.events.operations.LogEventBuildOperationProgressDetails import org.gradle.internal.logging.events.operations.ProgressStartBuildOperationProgressDetails import org.gradle.internal.logging.events.operations.StyledTextBuildOperationProgressDetails @@ -61,8 +62,6 @@ class LoggingBuildOperationProgressIntegTest extends AbstractIntegrationSpec { """ file("build.gradle") << """ - import java.util.concurrent.CountDownLatch - apply plugin: 'java' repositories { @@ -76,14 +75,6 @@ class LoggingBuildOperationProgressIntegTest extends AbstractIntegrationSpec { jar.doLast { println 'from jar task' } - - classes.doLast { - CountDownLatch latch = new CountDownLatch(1); - - def t = new Thread({ println 'from classes task external thread'; latch.countDown(); } as Runnable) - t.start() // Output: hello - latch.await(); - } task resolve { doLast { @@ -148,13 +139,84 @@ class LoggingBuildOperationProgressIntegTest extends AbstractIntegrationSpec { operations.parentsOf(downloadEvent).find { it.hasDetailsOfType(ExecuteTaskBuildOperationType.Details) && it.details.taskPath == ":resolve" } + } + + def "captures threaded output sources with context"() { + given: + executer.requireOwnGradleUserHomeDir() + settingsFile << """ + rootProject.name = 'root' + 10.times { + include "project-\${it}" + } + """ + file("build.gradle") << """ + import java.util.concurrent.CountDownLatch + + subprojects { + 10.times { + task("myTask\$it") { tsk -> + doLast { + threaded { + logger.lifecycle("from \${tsk.path} task external thread") + } + } + } + } + task all(dependsOn: tasks.matching{it.name.startsWith('myTask')}) { + doLast { + tasks.matching{it.name.startsWith('myTask')}.each { myTask -> + myTask.logger.lifecycle("log all task via \${myTask.path} logger") + } + } + } + + gradle.buildFinished { + tasks.all.logger.lifecycle("build finished from \${tasks.all.path}") + } + } + + threaded { + println("threaded configuration output") + } + + def threaded(Closure action) { + Thread.start(action).join() + } + """ + + when: + succeeds("all") + + then: + 10.times { projectCount -> + def allExecutionOp = operations.only("Execute doLast {} action for :project-${projectCount}:all") + def allExecutionOpTaskProgresses = allExecutionOp.progress + + 10.times { taskCount -> + def taskExecutionOp = operations.only("Task :project-${projectCount}:myTask$taskCount") + def classesTaskProgresses = taskExecutionOp.progress + def threadedTaskLoggingProgress = classesTaskProgresses.find { it.detailsType == LogEvent && it.details.message == "from :project-${projectCount}:myTask$taskCount task external thread" } + assert threadedTaskLoggingProgress.details.logLevel == 'LIFECYCLE' + + // logging done from task-a logger during task-b execution will result in logging linked to task-b + def allLoggingProgress = allExecutionOpTaskProgresses.find { it.detailsType == LogEvent && it.details.message == "log all task via :project-${projectCount}:myTask$taskCount logger" } + assert allLoggingProgress.details.logLevel == 'LIFECYCLE' + } + } def runBuildProgress = operations.only('Run build').progress - def threadedProgress = runBuildProgress.find { it.details.spans[0].text == "from classes task external thread${getPlatformLineSeparator()}" } - threadedProgress.details.category == 'system.out' - threadedProgress.details.spans.size == 1 - threadedProgress.details.spans[0].styleName == 'Normal' - threadedProgress.details.spans[0].text == "from classes task external thread${getPlatformLineSeparator()}" + def threadedConfigurationProgress = runBuildProgress.find { it.details.spans[0].text == "threaded configuration output${getPlatformLineSeparator()}" } + threadedConfigurationProgress.details.category == 'system.out' + threadedConfigurationProgress.details.spans.size == 1 + threadedConfigurationProgress.details.spans[0].styleName == 'Normal' + threadedConfigurationProgress.details.spans[0].text == "threaded configuration output${getPlatformLineSeparator()}" + + + // loggings from logger of finished task + 10.times { projectCount -> + runBuildProgress.find { it.detailsType == LogEvent && it.details.message == "build finished from :project-${projectCount}:all" } + } } def "captures output from buildSrc"() { @@ -364,7 +426,7 @@ class LoggingBuildOperationProgressIntegTest extends AbstractIntegrationSpec { assert nestedTaskProgress[0].details.spans[0].text == "foo println${getPlatformLineSeparator()}" assert nestedTaskProgress[1].details.logLevel == 'LIFECYCLE' - assert nestedTaskProgress[1].details.category == 'org.gradle.api.Task' + assert nestedTaskProgress[1].details.category == "org.gradle.api.Task" assert nestedTaskProgress[1].details.message == 'foo from logger' } diff --git a/subprojects/core/src/integTest/groovy/org/gradle/internal/operations/notify/BuildOperationNotificationIntegrationTest.groovy b/subprojects/core/src/integTest/groovy/org/gradle/internal/operations/notify/BuildOperationNotificationIntegrationTest.groovy index b05d4eb101243..18650da17e927 100644 --- a/subprojects/core/src/integTest/groovy/org/gradle/internal/operations/notify/BuildOperationNotificationIntegrationTest.groovy +++ b/subprojects/core/src/integTest/groovy/org/gradle/internal/operations/notify/BuildOperationNotificationIntegrationTest.groovy @@ -90,7 +90,7 @@ class BuildOperationNotificationIntegrationTest extends AbstractIntegrationSpec notifications.finished(CalculateTaskGraphBuildOperationType.Result, [excludedTaskPaths: [], requestedTaskPaths: [":t"]]) notifications.started(NotifyTaskGraphWhenReadyBuildOperationType.Details, [buildPath: ':']) notifications.started(ExecuteTaskBuildOperationType.Details, [taskPath: ":t", buildPath: ":", taskClass: "org.gradle.api.DefaultTask"]) - notifications.finished(ExecuteTaskBuildOperationType.Result, [actionable: false, originExecutionTime: null, cachingDisabledReasonMessage: "Cacheability was not determined", upToDateMessages: null, cachingDisabledReasonCategory: "UNKNOWN", skipMessage: "UP-TO-DATE", originBuildInvocationId: null]) + notifications.finished(ExecuteTaskBuildOperationType.Result, [actionable: false, originExecutionTime: null, cachingDisabledReasonMessage: "Cacheability was not determined", upToDateMessages: [], cachingDisabledReasonCategory: "UNKNOWN", skipMessage: "UP-TO-DATE", originBuildInvocationId: null]) } def "can emit notifications for nested builds"() { diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/AbstractTask.java b/subprojects/core/src/main/java/org/gradle/api/internal/AbstractTask.java index bedf3ec4fb7ca..1179ad25665bc 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/AbstractTask.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/AbstractTask.java @@ -33,16 +33,15 @@ import org.gradle.api.internal.file.TemporaryFileProvider; import org.gradle.api.internal.project.ProjectInternal; import org.gradle.api.internal.project.taskfactory.TaskIdentity; -import org.gradle.api.internal.tasks.ContextAwareTaskAction; import org.gradle.api.internal.tasks.DefaultTaskDependency; import org.gradle.api.internal.tasks.DefaultTaskDestroyables; import org.gradle.api.internal.tasks.DefaultTaskInputs; import org.gradle.api.internal.tasks.DefaultTaskLocalState; import org.gradle.api.internal.tasks.DefaultTaskOutputs; import org.gradle.api.internal.tasks.ImplementationAwareTaskAction; +import org.gradle.api.internal.tasks.InputChangesAwareTaskAction; import org.gradle.api.internal.tasks.TaskContainerInternal; import org.gradle.api.internal.tasks.TaskDependencyInternal; -import org.gradle.api.internal.tasks.TaskExecutionContext; import org.gradle.api.internal.tasks.TaskLocalStateInternal; import org.gradle.api.internal.tasks.TaskMutator; import org.gradle.api.internal.tasks.TaskStateInternal; @@ -61,8 +60,10 @@ import org.gradle.api.tasks.TaskLocalState; import org.gradle.internal.Factory; import org.gradle.internal.classloader.ClassLoaderHierarchyHasher; +import org.gradle.internal.execution.history.changes.InputChangesInternal; import org.gradle.internal.extensibility.ExtensibleDynamicObject; import org.gradle.internal.logging.compatbridge.LoggingManagerInternalCompatibilityBridge; +import org.gradle.internal.logging.slf4j.DefaultContextAwareTaskLogger; import org.gradle.internal.metaobject.DynamicObject; import org.gradle.internal.reflect.Instantiator; import org.gradle.internal.scripts.ScriptOrigin; @@ -95,7 +96,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware { private final ProjectInternal project; - private List actions; + private List actions; private boolean enabled = true; @@ -121,7 +122,7 @@ public abstract class AbstractTask implements TaskInternal, DynamicObjectAware { private final TaskStateInternal state; - private Logger logger = BUILD_LOGGER; + private Logger logger = new DefaultContextAwareTaskLogger(BUILD_LOGGER); private final TaskMutator taskMutator; private ObservableList observableActionList; @@ -227,9 +228,9 @@ public void propertyChange(PropertyChangeEvent evt) { } @Override - public List getTaskActions() { + public List getTaskActions() { if (actions == null) { - actions = new ArrayList(3); + actions = new ArrayList(3); } return actions; } @@ -589,17 +590,17 @@ public File create() { }; } - private ContextAwareTaskAction convertClosureToAction(Closure actionClosure, String actionName) { + private InputChangesAwareTaskAction convertClosureToAction(Closure actionClosure, String actionName) { return new ClosureTaskAction(actionClosure, actionName); } - private ContextAwareTaskAction wrap(final Action action) { + private InputChangesAwareTaskAction wrap(final Action action) { return wrap(action, "unnamed action"); } - private ContextAwareTaskAction wrap(final Action action, String actionName) { - if (action instanceof ContextAwareTaskAction) { - return (ContextAwareTaskAction) action; + private InputChangesAwareTaskAction wrap(final Action action, String actionName) { + if (action instanceof InputChangesAwareTaskAction) { + return (InputChangesAwareTaskAction) action; } return new TaskActionWrapper(action, actionName); } @@ -614,7 +615,7 @@ private TaskInfo(TaskIdentity identity, ProjectInternal project) { } } - private static class ClosureTaskAction implements ContextAwareTaskAction { + private static class ClosureTaskAction implements InputChangesAwareTaskAction { private final Closure closure; private final String actionName; @@ -624,11 +625,11 @@ private ClosureTaskAction(Closure closure, String actionName) { } @Override - public void contextualise(TaskExecutionContext context) { + public void setInputChanges(InputChangesInternal inputChanges) { } @Override - public void releaseContext() { + public void clearInputChanges() { } @Override @@ -665,7 +666,7 @@ public String getDisplayName() { } } - private static class TaskActionWrapper implements ContextAwareTaskAction { + private static class TaskActionWrapper implements InputChangesAwareTaskAction { private final Action action; private final String maybeActionName; @@ -680,16 +681,16 @@ public TaskActionWrapper(Action action, String maybeActionName) { } @Override - public void contextualise(TaskExecutionContext context) { - if (action instanceof ContextAwareTaskAction) { - ((ContextAwareTaskAction) action).contextualise(context); + public void setInputChanges(InputChangesInternal inputChanges) { + if (action instanceof InputChangesAwareTaskAction) { + ((InputChangesAwareTaskAction) action).setInputChanges(inputChanges); } } @Override - public void releaseContext() { - if (action instanceof ContextAwareTaskAction) { - ((ContextAwareTaskAction) action).releaseContext(); + public void clearInputChanges() { + if (action instanceof InputChangesAwareTaskAction) { + ((InputChangesAwareTaskAction) action).clearInputChanges(); } } @@ -872,9 +873,9 @@ public boolean remove(Object action) { return super.remove(wrap((Action) action)); } - private Collection transformToContextAwareTaskActions(Collection c) { - return Collections2.transform(c, new Function() { - public ContextAwareTaskAction apply(@Nullable Object input) { + private Collection transformToContextAwareTaskActions(Collection c) { + return Collections2.transform(c, new Function() { + public InputChangesAwareTaskAction apply(@Nullable Object input) { return wrap((Action) input); } }); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/CompositeDomainObjectSet.java b/subprojects/core/src/main/java/org/gradle/api/internal/CompositeDomainObjectSet.java index 8bf1c243067f5..d8a77c82c048a 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/CompositeDomainObjectSet.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/CompositeDomainObjectSet.java @@ -48,6 +48,7 @@ public static CompositeDomainObjectSet create(Class type, DomainObject return create(type, CollectionCallbackActionDecorator.NOOP, collections); } + @SafeVarargs public static CompositeDomainObjectSet create(Class type, CollectionCallbackActionDecorator callbackActionDecorator, DomainObjectCollection... collections) { DefaultDomainObjectSet backingSet = new DefaultDomainObjectSet(type, new DomainObjectCompositeCollection(), callbackActionDecorator); CompositeDomainObjectSet out = new CompositeDomainObjectSet(backingSet, callbackActionDecorator); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/DefaultClassPathProvider.java b/subprojects/core/src/main/java/org/gradle/api/internal/DefaultClassPathProvider.java index 5746767474af0..a89ac310da63d 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/DefaultClassPathProvider.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/DefaultClassPathProvider.java @@ -32,9 +32,6 @@ public ClassPath findClassPath(String name) { if (name.equals("GRADLE_INSTALLATION_BEACON")) { return moduleRegistry.getModule("gradle-installation-beacon").getImplementationClasspath(); } - if (name.equals("COMMONS_CLI")) { - return moduleRegistry.getExternalModule("commons-cli").getClasspath(); - } if (name.equals("ANT")) { ClassPath classpath = ClassPath.EMPTY; classpath = classpath.plus(moduleRegistry.getExternalModule("ant").getClasspath()); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/FeaturePreviews.java b/subprojects/core/src/main/java/org/gradle/api/internal/FeaturePreviews.java index ec33750d8b3ab..157a448bccd1d 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/FeaturePreviews.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/FeaturePreviews.java @@ -29,7 +29,7 @@ public enum Feature { IMPROVED_POM_SUPPORT(false), GRADLE_METADATA(true), STABLE_PUBLISHING(false), - INCREMENTAL_ARTIFACT_TRANSFORMATIONS(true); + INCREMENTAL_ARTIFACT_TRANSFORMATIONS(false); public static Feature withName(String name) { try { diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/TaskInternal.java b/subprojects/core/src/main/java/org/gradle/api/internal/TaskInternal.java index c9bea9245c4ff..73edce572febe 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/TaskInternal.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/TaskInternal.java @@ -19,7 +19,7 @@ import org.gradle.api.Action; import org.gradle.api.Task; import org.gradle.api.internal.project.taskfactory.TaskIdentity; -import org.gradle.api.internal.tasks.ContextAwareTaskAction; +import org.gradle.api.internal.tasks.InputChangesAwareTaskAction; import org.gradle.api.internal.tasks.TaskStateInternal; import org.gradle.api.logging.Logger; import org.gradle.api.specs.Spec; @@ -40,7 +40,7 @@ public interface TaskInternal extends Task, Configurable { * once they start executing. */ @Internal - List getTaskActions(); + List getTaskActions(); @Internal boolean hasTaskActions(); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/TaskOutputCachingState.java b/subprojects/core/src/main/java/org/gradle/api/internal/TaskOutputCachingState.java deleted file mode 100644 index 2bc677d7611be..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/TaskOutputCachingState.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal; - -import org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory; - -import javax.annotation.Nullable; - -public interface TaskOutputCachingState { - /** - * Check if caching is enabled for the task outputs. - */ - boolean isEnabled(); - - /** - * Returns the reason why caching is disabled for the task. - */ - @Nullable - String getDisabledReason(); - - /** - * Returns the category of the reason why caching is disabled for the task. - */ - @Nullable - TaskOutputCachingDisabledReasonCategory getDisabledReasonCategory(); -} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/artifacts/JavaEcosystemSupport.java b/subprojects/core/src/main/java/org/gradle/api/internal/artifacts/JavaEcosystemSupport.java index c0bcc40fed59a..84c37aecfe2fd 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/artifacts/JavaEcosystemSupport.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/artifacts/JavaEcosystemSupport.java @@ -17,17 +17,22 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Ordering; import org.gradle.api.Action; import org.gradle.api.ActionConfiguration; +import org.gradle.api.JavaVersion; import org.gradle.api.attributes.AttributeCompatibilityRule; import org.gradle.api.attributes.AttributeDisambiguationRule; import org.gradle.api.attributes.AttributeMatchingStrategy; import org.gradle.api.attributes.AttributesSchema; import org.gradle.api.attributes.CompatibilityCheckDetails; +import org.gradle.api.attributes.HasAttributes; import org.gradle.api.attributes.MultipleCandidatesDetails; import org.gradle.api.attributes.Usage; import org.gradle.api.attributes.Bundling; +import org.gradle.api.attributes.java.TargetJvmVersion; import org.gradle.api.internal.ReusableAction; +import org.gradle.api.internal.attributes.AttributeContainerInternal; import org.gradle.api.model.ObjectFactory; import javax.inject.Inject; @@ -35,6 +40,33 @@ public abstract class JavaEcosystemSupport { public static void configureSchema(AttributesSchema attributesSchema, final ObjectFactory objectFactory) { + configureUsage(attributesSchema, objectFactory); + configureBundling(attributesSchema); + configureTargetPlatform(attributesSchema); + } + + public static void configureDefaultTargetPlatform(HasAttributes configuration, JavaVersion version) { + String majorVersion = version.getMajorVersion(); + AttributeContainerInternal attributes = (AttributeContainerInternal) configuration.getAttributes(); + // If nobody said anything about this variant's target platform, use whatever the convention says + if (!attributes.contains(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE)) { + attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, Integer.valueOf(majorVersion)); + } + } + + private static void configureTargetPlatform(AttributesSchema attributesSchema) { + AttributeMatchingStrategy targetPlatformSchema = attributesSchema.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE); + targetPlatformSchema.getCompatibilityRules().ordered(Ordering.natural()); + targetPlatformSchema.getDisambiguationRules().pickLast(Ordering.natural()); + } + + private static void configureBundling(AttributesSchema attributesSchema) { + AttributeMatchingStrategy bundlingSchema = attributesSchema.attribute(Bundling.BUNDLING_ATTRIBUTE); + bundlingSchema.getCompatibilityRules().add(BundlingCompatibilityRules.class); + bundlingSchema.getDisambiguationRules().add(BundlingDisambiguationRules.class); + } + + private static void configureUsage(AttributesSchema attributesSchema, final ObjectFactory objectFactory) { AttributeMatchingStrategy usageSchema = attributesSchema.attribute(Usage.USAGE_ATTRIBUTE); usageSchema.getCompatibilityRules().add(UsageCompatibilityRules.class); usageSchema.getDisambiguationRules().add(UsageDisambiguationRules.class, new Action() { @@ -49,9 +81,6 @@ public void execute(ActionConfiguration actionConfiguration) { actionConfiguration.params(objectFactory.named(Usage.class, Usage.JAVA_RUNTIME_RESOURCES)); } }); - AttributeMatchingStrategy bundlingSchema = attributesSchema.attribute(Bundling.BUNDLING_ATTRIBUTE); - bundlingSchema.getCompatibilityRules().add(BundlingCompatibilityRules.class); - bundlingSchema.getDisambiguationRules().add(BundlingDisambiguationRules.class); } @VisibleForTesting @@ -111,6 +140,10 @@ public void execute(MultipleCandidatesDetails details) { } else if (candidateValues.contains(javaApi)) { // Prefer the API over the runtime when the API has been requested details.closestMatch(javaApi); + } else if (candidateValues.contains(javaRuntimeClasses)) { + details.closestMatch(javaRuntimeClasses); + } else if (candidateValues.contains(javaRuntimeJars)) { + details.closestMatch(javaRuntimeJars); } } else if (runtimeVariants.contains(consumerValue)) { // we're asking for a runtime variant, but no exact match was found @@ -270,4 +303,5 @@ public void execute(MultipleCandidatesDetails details) { } } } + } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/changes/ChangesOnlyIncrementalTaskInputs.java b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/changes/ChangesOnlyIncrementalTaskInputs.java index 52901bcf649ee..2907471675f19 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/changes/ChangesOnlyIncrementalTaskInputs.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/changes/ChangesOnlyIncrementalTaskInputs.java @@ -18,16 +18,15 @@ import org.gradle.api.Action; import org.gradle.api.tasks.incremental.InputFileDetails; -import org.gradle.internal.change.Change; import java.util.ArrayList; import java.util.List; public class ChangesOnlyIncrementalTaskInputs extends StatefulIncrementalTaskInputs { - private final Iterable inputFilesState; + private final Iterable inputFilesState; private final List removedFiles = new ArrayList(); - public ChangesOnlyIncrementalTaskInputs(Iterable inputFilesState) { + public ChangesOnlyIncrementalTaskInputs(Iterable inputFilesState) { this.inputFilesState = inputFilesState; } @@ -37,8 +36,7 @@ public boolean isIncremental() { @Override protected void doOutOfDate(final Action outOfDateAction) { - for (Change change : inputFilesState) { - InputFileDetails fileChange = (InputFileDetails) change; + for (InputFileDetails fileChange : inputFilesState) { if (fileChange.isRemoved()) { removedFiles.add(fileChange); } else { diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/changes/RebuildIncrementalTaskInputs.java b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/changes/RebuildIncrementalTaskInputs.java index faf56ad06835f..5f77306a69a42 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/changes/RebuildIncrementalTaskInputs.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/changes/RebuildIncrementalTaskInputs.java @@ -17,22 +17,13 @@ package org.gradle.api.internal.changedetection.changes; import org.gradle.api.Action; -import org.gradle.api.Task; import org.gradle.api.tasks.incremental.InputFileDetails; -import org.gradle.internal.fingerprint.FileCollectionFingerprint; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; public class RebuildIncrementalTaskInputs extends StatefulIncrementalTaskInputs { - private static final Logger LOGGER = LoggerFactory.getLogger(RebuildIncrementalTaskInputs.class); - - private final Iterable fingerprints; + private final Iterable inputChanges; - public RebuildIncrementalTaskInputs(Task task, Iterable fingerprints) { - LOGGER.info("All input files are considered out-of-date for incremental {}.", task); - this.fingerprints = fingerprints; + public RebuildIncrementalTaskInputs(Iterable inputChanges) { + this.inputChanges = inputChanges; } public boolean isIncremental() { @@ -40,37 +31,11 @@ public boolean isIncremental() { } public void doOutOfDate(final Action outOfDateAction) { - for (FileCollectionFingerprint fingerprint : fingerprints) { - for (String path : fingerprint.getFingerprints().keySet()) { - outOfDateAction.execute(new RebuildInputFile(new File(path))); - } + for (InputFileDetails inputFileChange : inputChanges) { + outOfDateAction.execute(inputFileChange); } } public void doRemoved(Action removedAction) { } - - private static class RebuildInputFile implements InputFileDetails { - private final File file; - - private RebuildInputFile(File file) { - this.file = file; - } - - public File getFile() { - return file; - } - - public boolean isAdded() { - return false; - } - - public boolean isModified() { - return false; - } - - public boolean isRemoved() { - return false; - } - } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/AbiExtractingClasspathResourceHasher.java b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/AbiExtractingClasspathResourceHasher.java index 90be934ba1dbf..427293ce8f2e5 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/AbiExtractingClasspathResourceHasher.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/AbiExtractingClasspathResourceHasher.java @@ -17,14 +17,14 @@ import com.google.common.io.ByteStreams; import org.gradle.api.internal.tasks.compile.ApiClassExtractor; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.IoActions; import org.gradle.internal.hash.HashCode; import org.gradle.internal.hash.Hasher; import org.gradle.internal.hash.Hashing; import org.gradle.internal.snapshot.RegularFileSnapshot; import org.objectweb.asm.ClassReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.io.IOException; @@ -36,7 +36,7 @@ import java.util.zip.ZipEntry; public class AbiExtractingClasspathResourceHasher implements ResourceHasher { - private static final Logger LOGGER = Logging.getLogger(AbiExtractingClasspathResourceHasher.class); + private static final Logger LOGGER = LoggerFactory.getLogger(AbiExtractingClasspathResourceHasher.class); private HashCode hashClassBytes(InputStream inputStream) throws IOException { // Use the ABI as the hash diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/AbstractTaskExecution.java b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/AbstractTaskExecution.java deleted file mode 100644 index 89fb23ab4a66f..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/AbstractTaskExecution.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2011 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.gradle.api.internal.changedetection.state; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; -import org.gradle.internal.snapshot.ValueSnapshot; -import org.gradle.internal.snapshot.impl.ImplementationSnapshot; - -public abstract class AbstractTaskExecution implements TaskExecution { - - private final ImplementationSnapshot implementation; - private final ImmutableList additionalImplementations; - private final ImmutableSortedMap inputProperties; - - public AbstractTaskExecution( - ImplementationSnapshot implementation, - ImmutableList additionalImplementations, - ImmutableSortedMap inputProperties - ) { - this.implementation = implementation; - this.additionalImplementations = additionalImplementations; - this.inputProperties = inputProperties; - } - - @Override - public ImplementationSnapshot getImplementation() { - return implementation; - } - - @Override - public ImmutableList getAdditionalImplementations() { - return additionalImplementations; - } - - @Override - public ImmutableSortedMap getInputProperties() { - return inputProperties; - } - -} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/JarHasher.java b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/JarHasher.java index 406b85487579a..aded92e29620a 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/JarHasher.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/changedetection/state/JarHasher.java @@ -17,9 +17,9 @@ package org.gradle.api.internal.changedetection.state; import com.google.common.collect.Lists; +import org.gradle.api.file.internal.FilePathUtil; import org.gradle.internal.Factory; import org.gradle.internal.IoActions; -import org.gradle.internal.file.FilePathUtil; import org.gradle.internal.file.FileType; import org.gradle.internal.fingerprint.FileSystemLocationFingerprint; import org.gradle.internal.fingerprint.impl.DefaultFileSystemLocationFingerprint; @@ -64,6 +64,7 @@ public void appendConfigurationToHasher(Hasher hasher) { classpathResourceFilter.appendConfigurationToHasher(hasher); } + @Nullable private HashCode hashJarContents(RegularFileSnapshot jarFileSnapshot) { try { List fingerprints = fingerprintZipEntries(jarFileSnapshot.getAbsolutePath()); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/classloading/ClassInfoCleaningGroovySystemLoader.java b/subprojects/core/src/main/java/org/gradle/api/internal/classloading/ClassInfoCleaningGroovySystemLoader.java index 75522f3f3af06..db70c0b30c976 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/classloading/ClassInfoCleaningGroovySystemLoader.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/classloading/ClassInfoCleaningGroovySystemLoader.java @@ -17,8 +17,8 @@ package org.gradle.api.internal.classloading; import org.gradle.api.GradleException; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.ref.WeakReference; import java.lang.reflect.Field; @@ -28,7 +28,7 @@ public class ClassInfoCleaningGroovySystemLoader implements GroovySystemLoader { - private final static Logger LOG = Logging.getLogger(ClassInfoCleaningGroovySystemLoader.class); + private final static Logger LOG = LoggerFactory.getLogger(ClassInfoCleaningGroovySystemLoader.class); private final Method removeFromGlobalClassValue; private final Method globalClassSetIteratorMethod; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CachingContext.java b/subprojects/core/src/main/java/org/gradle/api/internal/component/IvyPublishingAwareContext.java similarity index 70% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CachingContext.java rename to subprojects/core/src/main/java/org/gradle/api/internal/component/IvyPublishingAwareContext.java index 836a721e191e1..02d4bfa69e539 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CachingContext.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/component/IvyPublishingAwareContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,9 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.api.internal.component; -import org.gradle.internal.execution.CacheHandler; +public interface IvyPublishingAwareContext extends UsageContext { -public interface CachingContext extends Context { - CacheHandler getCacheHandler(); + boolean isOptional(); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/file/DefaultFilePropertyFactory.java b/subprojects/core/src/main/java/org/gradle/api/internal/file/DefaultFilePropertyFactory.java index dc116e14d9008..cb85b60196319 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/file/DefaultFilePropertyFactory.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/file/DefaultFilePropertyFactory.java @@ -30,6 +30,7 @@ import org.gradle.api.internal.tasks.TaskDependencyResolveContext; import org.gradle.api.provider.Provider; import org.gradle.internal.file.PathToFileResolver; +import org.gradle.internal.state.Managed; import java.io.File; @@ -50,7 +51,7 @@ public RegularFileProperty newFileProperty() { return new DefaultRegularFileVar(fileResolver); } - static class FixedDirectory implements Directory { + static class FixedDirectory implements Directory, Managed { private final File value; final FileResolver fileResolver; @@ -59,11 +60,56 @@ static class FixedDirectory implements Directory { this.fileResolver = fileResolver; } + @Override + public boolean immutable() { + return true; + } + + @Override + public Class publicType() { + return Directory.class; + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Override + public T fromState(Class type, Object state) { + if (!type.isAssignableFrom(Directory.class)) { + return null; + } + return type.cast(new FixedDirectory((File) state, fileResolver)); + } + }; + } + + @Override + public Object unpackState() { + return value; + } + @Override public String toString() { return value.toString(); } + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != getClass()) { + return false; + } + FixedDirectory other = (FixedDirectory) obj; + return value.equals(other.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + @Override public File getAsFile() { return value; @@ -96,18 +142,63 @@ public Provider file(Provider path) { } } - static class FixedFile implements RegularFile { + static class FixedFile implements RegularFile, Managed { private final File file; FixedFile(File file) { this.file = file; } + @Override + public boolean immutable() { + return true; + } + + @Override + public Class publicType() { + return RegularFile.class; + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Override + public T fromState(Class type, Object state) { + if (!type.isAssignableFrom(RegularFile.class)) { + return null; + } + return type.cast(new FixedFile((File) state)); + } + }; + } + + @Override + public Object unpackState() { + return file; + } + @Override public String toString() { return file.toString(); } + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != getClass()) { + return false; + } + FixedFile other = (FixedFile) obj; + return other.file.equals(file); + } + + @Override + public int hashCode() { + return file.hashCode(); + } + @Override public File getAsFile() { return file; @@ -166,7 +257,7 @@ public void setFromAnyValue(Object object) { public abstract void set(File file); } - static class DefaultRegularFileVar extends AbstractFileVar implements RegularFileProperty { + static class DefaultRegularFileVar extends AbstractFileVar implements RegularFileProperty, Managed { private final PathToFileResolver fileResolver; DefaultRegularFileVar(PathToFileResolver fileResolver) { @@ -174,6 +265,24 @@ static class DefaultRegularFileVar extends AbstractFileVar implemen this.fileResolver = fileResolver; } + @Override + public Class publicType() { + return RegularFileProperty.class; + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Override + public T fromState(Class type, Object state) { + if (!type.isAssignableFrom(RegularFileProperty.class)) { + return null; + } + return type.cast(new DefaultRegularFileVar(fileResolver).value((RegularFile) state)); + } + }; + } + @Override public Provider getAsFile() { return new ToFileProvider(this); @@ -181,6 +290,10 @@ public Provider getAsFile() { @Override public void set(File file) { + if (file == null) { + value(null); + return; + } set(new FixedFile(fileResolver.resolve(file))); } @@ -218,7 +331,7 @@ protected Directory map(CharSequence path) { } } - static class DefaultDirectoryVar extends AbstractFileVar implements DirectoryProperty { + static class DefaultDirectoryVar extends AbstractFileVar implements DirectoryProperty, Managed { private final FileResolver resolver; DefaultDirectoryVar(FileResolver resolver) { @@ -232,6 +345,24 @@ static class DefaultDirectoryVar extends AbstractFileVar implements D resolveAndSet(value); } + @Override + public Class publicType() { + return DirectoryProperty.class; + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Override + public T fromState(Class type, Object state) { + if (!type.isAssignableFrom(DirectoryProperty.class)) { + return null; + } + return type.cast(new DefaultDirectoryVar(resolver).value((Directory) state)); + } + }; + } + @Override public FileTree getAsFileTree() { return resolver.resolveFilesAsTree(this); @@ -249,6 +380,10 @@ void resolveAndSet(Object value) { @Override public void set(File dir) { + if (dir == null) { + value(null); + return; + } File resolved = resolver.resolve(dir); set(new FixedDirectory(resolved, resolver.newResolver(resolved))); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/file/copy/SyncCopyActionDecorator.java b/subprojects/core/src/main/java/org/gradle/api/internal/file/copy/SyncCopyActionDecorator.java index 002d9dd99ea38..ebc1530900204 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/file/copy/SyncCopyActionDecorator.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/file/copy/SyncCopyActionDecorator.java @@ -29,6 +29,7 @@ import org.gradle.api.tasks.util.PatternSet; import org.gradle.util.GFileUtils; +import javax.annotation.Nullable; import java.io.File; import java.util.HashSet; import java.util.Set; @@ -79,7 +80,7 @@ private static class SyncCopyActionDecoratorFileVisitor implements FileVisitor { private final PatternSet preserveSet; private boolean didWork; - private SyncCopyActionDecoratorFileVisitor(Set visited, PatternFilterable preserveSpec) { + private SyncCopyActionDecoratorFileVisitor(Set visited, @Nullable PatternFilterable preserveSpec) { this.visited = visited; PatternSet preserveSet = new PatternSet(); if (preserveSpec != null) { @@ -102,11 +103,7 @@ private void maybeDelete(FileVisitDetails fileDetails, boolean isDir) { RelativePath path = fileDetails.getRelativePath(); if (!visited.contains(path)) { if (preserveSet.isEmpty() || !preserveSpec.isSatisfiedBy(fileDetails)) { - if (isDir) { - GFileUtils.deleteDirectory(fileDetails.getFile()); - } else { - GFileUtils.deleteQuietly(fileDetails.getFile()); - } + GFileUtils.forceDelete(fileDetails.getFile()); didWork = true; } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/initialization/DefaultScriptHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/initialization/DefaultScriptHandler.java index 6312470dc029f..c0e732d1703c6 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/initialization/DefaultScriptHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/initialization/DefaultScriptHandler.java @@ -16,12 +16,15 @@ package org.gradle.api.internal.initialization; import groovy.lang.Closure; +import org.gradle.api.JavaVersion; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.dsl.RepositoryHandler; import org.gradle.api.attributes.Bundling; +import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.attributes.Usage; +import org.gradle.api.attributes.java.TargetJvmVersion; import org.gradle.api.initialization.dsl.ScriptHandler; import org.gradle.api.internal.DynamicObjectAware; import org.gradle.api.internal.artifacts.DependencyResolutionServices; @@ -112,8 +115,10 @@ private void defineConfiguration() { } if (classpathConfiguration == null) { classpathConfiguration = configContainer.create(CLASSPATH_CONFIGURATION); - classpathConfiguration.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, NamedObjectInstantiator.INSTANCE.named(Usage.class, Usage.JAVA_RUNTIME)); - classpathConfiguration.getAttributes().attribute(Bundling.BUNDLING_ATTRIBUTE, NamedObjectInstantiator.INSTANCE.named(Bundling.class, Bundling.EXTERNAL)); + AttributeContainer attributes = classpathConfiguration.getAttributes(); + attributes.attribute(Usage.USAGE_ATTRIBUTE, NamedObjectInstantiator.INSTANCE.named(Usage.class, Usage.JAVA_RUNTIME)); + attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, NamedObjectInstantiator.INSTANCE.named(Bundling.class, Bundling.EXTERNAL)); + attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, Integer.valueOf(JavaVersion.current().getMajorVersion())); } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/initialization/loadercache/DefaultClassLoaderCache.java b/subprojects/core/src/main/java/org/gradle/api/internal/initialization/loadercache/DefaultClassLoaderCache.java index ad8651fc2578a..3d1c41663d80c 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/initialization/loadercache/DefaultClassLoaderCache.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/initialization/loadercache/DefaultClassLoaderCache.java @@ -23,8 +23,6 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multiset; import com.google.common.collect.Sets; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.initialization.SessionLifecycleListener; import org.gradle.internal.classloader.ClassLoaderUtils; import org.gradle.internal.classloader.ClasspathHasher; @@ -33,13 +31,15 @@ import org.gradle.internal.classpath.ClassPath; import org.gradle.internal.concurrent.Stoppable; import org.gradle.internal.hash.HashCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nullable; import java.util.Map; import java.util.Set; public class DefaultClassLoaderCache implements ClassLoaderCache, Stoppable, SessionLifecycleListener { - private static final Logger LOGGER = Logging.getLogger(DefaultClassLoaderCache.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultClassLoaderCache.class); private final Object lock = new Object(); private final Map byId = Maps.newHashMap(); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/plugins/DefaultPluginManager.java b/subprojects/core/src/main/java/org/gradle/api/internal/plugins/DefaultPluginManager.java index da9567dbb1a05..d1d89a4aba84c 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/plugins/DefaultPluginManager.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/plugins/DefaultPluginManager.java @@ -18,7 +18,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import net.jcip.annotations.NotThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; import org.gradle.api.Action; import org.gradle.api.DomainObjectSet; import org.gradle.api.Plugin; diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/plugins/PluginInspector.java b/subprojects/core/src/main/java/org/gradle/api/internal/plugins/PluginInspector.java index 9dfcf7a9777dd..7853af662f00e 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/plugins/PluginInspector.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/plugins/PluginInspector.java @@ -16,7 +16,7 @@ package org.gradle.api.internal.plugins; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Plugin; import org.gradle.internal.Cast; import org.gradle.model.internal.inspect.ModelRuleSourceDetector; diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/plugins/PluginRegistry.java b/subprojects/core/src/main/java/org/gradle/api/internal/plugins/PluginRegistry.java index ccbd926a6f3a3..9ad8c8a95f0f7 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/plugins/PluginRegistry.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/plugins/PluginRegistry.java @@ -16,7 +16,7 @@ package org.gradle.api.internal.plugins; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.internal.initialization.ClassLoaderScope; import org.gradle.plugin.use.PluginId; diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/DeferredProjectConfiguration.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/DeferredProjectConfiguration.java index c1f59f6a11ba2..502ba0590fbe3 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/project/DeferredProjectConfiguration.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/DeferredProjectConfiguration.java @@ -17,7 +17,7 @@ package org.gradle.api.internal.project; import com.google.common.collect.Lists; -import net.jcip.annotations.NotThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; import org.gradle.api.Project; import java.util.List; diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/antbuilder/ClassPathToClassLoaderCache.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/antbuilder/ClassPathToClassLoaderCache.java index bb933bfe068ad..8bf2aa927bc94 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/project/antbuilder/ClassPathToClassLoaderCache.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/antbuilder/ClassPathToClassLoaderCache.java @@ -20,12 +20,12 @@ import org.gradle.api.Action; import org.gradle.api.internal.classloading.GroovySystemLoader; import org.gradle.api.internal.classloading.GroovySystemLoaderFactory; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.Factory; import org.gradle.internal.UncheckedException; import org.gradle.internal.classpath.ClassPath; import org.gradle.internal.concurrent.Stoppable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Map; import java.util.Set; @@ -42,7 +42,7 @@ * the cache. */ public class ClassPathToClassLoaderCache implements Stoppable { - private final static Logger LOG = Logging.getLogger(ClassPathToClassLoaderCache.class); + private final static Logger LOG = LoggerFactory.getLogger(ClassPathToClassLoaderCache.class); private final FinalizerThread finalizerThread; diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/antbuilder/FinalizerThread.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/antbuilder/FinalizerThread.java index d0b82f4c47d28..5417fdae3b506 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/project/antbuilder/FinalizerThread.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/antbuilder/FinalizerThread.java @@ -16,9 +16,9 @@ package org.gradle.api.internal.project.antbuilder; import com.google.common.collect.Maps; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.classpath.ClassPath; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.ref.ReferenceQueue; import java.util.Map; @@ -29,7 +29,7 @@ import static org.gradle.api.internal.project.antbuilder.Cleanup.Mode.DONT_CLOSE_CLASSLOADER; class FinalizerThread extends Thread { - private final static Logger LOG = Logging.getLogger(FinalizerThread.class); + private final static Logger LOG = LoggerFactory.getLogger(FinalizerThread.class); private final ReferenceQueue referenceQueue; private final AtomicBoolean stopped = new AtomicBoolean(); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/AbstractIncrementalTaskAction.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/AbstractIncrementalTaskAction.java new file mode 100644 index 0000000000000..abcd23bfdedbb --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/AbstractIncrementalTaskAction.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.project.taskfactory; + +import org.gradle.api.Task; +import org.gradle.api.internal.tasks.InputChangesAwareTaskAction; +import org.gradle.internal.execution.history.changes.InputChangesInternal; + +import java.lang.reflect.Method; + +public abstract class AbstractIncrementalTaskAction extends StandardTaskAction implements InputChangesAwareTaskAction { + private InputChangesInternal inputChanges; + + public AbstractIncrementalTaskAction(Class type, Method method) { + super(type, method); + } + + @Override + public void setInputChanges(InputChangesInternal inputChanges) { + this.inputChanges = inputChanges; + } + + @Override + public void clearInputChanges() { + this.inputChanges = null; + } + + protected InputChangesInternal getInputChanges() { + return inputChanges; + } +} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/DefaultTaskClassInfoStore.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/DefaultTaskClassInfoStore.java index ed39c80fcf6a9..fd239b9a88017 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/DefaultTaskClassInfoStore.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/DefaultTaskClassInfoStore.java @@ -29,6 +29,7 @@ import org.gradle.cache.internal.CrossBuildInMemoryCache; import org.gradle.cache.internal.CrossBuildInMemoryCacheFactory; import org.gradle.internal.reflect.Instantiator; +import org.gradle.work.InputChanges; import javax.annotation.Nullable; import java.lang.reflect.Method; @@ -65,9 +66,9 @@ private TaskClassInfo createTaskClassInfo(Class type) { if (taskActionFactory == null) { continue; } - if (taskActionFactory instanceof IncrementalTaskActionFactory) { + if (taskActionFactory instanceof AbstractIncrementalTaskActionFactory) { if (incremental) { - throw new GradleException(String.format("Cannot have multiple @TaskAction methods accepting an %s parameter.", IncrementalTaskInputs.class.getSimpleName())); + throw new GradleException(String.format("Cannot have multiple @TaskAction methods accepting an %s or %s parameter.", InputChanges.class.getSimpleName(), IncrementalTaskInputs.class.getSimpleName())); } incremental = true; } @@ -97,12 +98,16 @@ private static TaskActionFactory createTaskAction(Class taskType TaskActionFactory taskActionFactory; if (parameterTypes.length == 1) { - if (!parameterTypes[0].equals(IncrementalTaskInputs.class)) { + Class parameterType = parameterTypes[0]; + if (parameterType.equals(IncrementalTaskInputs.class)) { + taskActionFactory = new IncrementalTaskInputsTaskActionFactory(taskType, method); + } else if (parameterType.equals(InputChanges.class)) { + taskActionFactory = new IncrementalInputsTaskActionFactory(taskType, method); + } else { throw new GradleException(String.format( "Cannot use @TaskAction annotation on method %s.%s() because %s is not a valid parameter to an action method.", - declaringClass.getSimpleName(), method.getName(), parameterTypes[0])); + declaringClass.getSimpleName(), method.getName(), parameterType)); } - taskActionFactory = new IncrementalTaskActionFactory(taskType, method); } else { taskActionFactory = new StandardTaskActionFactory(taskType, method); } @@ -134,18 +139,42 @@ public Action create(Instantiator instantiator) { } } - private static class IncrementalTaskActionFactory implements TaskActionFactory { + private static class IncrementalInputsTaskActionFactory extends AbstractIncrementalTaskActionFactory { + public IncrementalInputsTaskActionFactory(Class taskType, Method method) { + super(taskType, method); + } + + @Override + protected Action doCreate(Instantiator instantiator, Class taskType, Method method) { + return new IncrementalInputsTaskAction(taskType, method); + } + } + + private static class IncrementalTaskInputsTaskActionFactory extends AbstractIncrementalTaskActionFactory { + public IncrementalTaskInputsTaskActionFactory(Class taskType, Method method) { + super(taskType, method); + } + + @Override + protected Action doCreate(Instantiator instantiator, Class taskType, Method method) { + return new IncrementalTaskInputsTaskAction(instantiator, taskType, method); + } + } + + private static abstract class AbstractIncrementalTaskActionFactory implements TaskActionFactory { private final Class taskType; private final Method method; - public IncrementalTaskActionFactory(Class taskType, Method method) { + public AbstractIncrementalTaskActionFactory(Class taskType, Method method) { this.taskType = taskType; this.method = method; } + protected abstract Action doCreate(Instantiator instantiator, Class taskType, Method method); + @Override public Action create(Instantiator instantiator) { - return new IncrementalTaskAction(instantiator, taskType, method); + return doCreate(instantiator, taskType, method); } } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalInputsTaskAction.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalInputsTaskAction.java new file mode 100644 index 0000000000000..ba67030eead39 --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalInputsTaskAction.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.project.taskfactory; + +import org.gradle.api.Task; +import org.gradle.internal.reflect.JavaMethod; +import org.gradle.work.InputChanges; + +import java.lang.reflect.Method; + +public class IncrementalInputsTaskAction extends AbstractIncrementalTaskAction { + public IncrementalInputsTaskAction(Class type, Method method) { + super(type, method); + } + + protected void doExecute(Task task, String methodName) { + JavaMethod.of(task, Object.class, methodName, InputChanges.class).invoke(task, getInputChanges()); + } +} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalTaskAction.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalTaskAction.java deleted file mode 100644 index baaa26d47b831..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalTaskAction.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.project.taskfactory; - -import com.google.common.collect.ImmutableCollection; -import org.gradle.api.Task; -import org.gradle.api.internal.changedetection.changes.ChangesOnlyIncrementalTaskInputs; -import org.gradle.api.internal.changedetection.changes.RebuildIncrementalTaskInputs; -import org.gradle.api.internal.changedetection.changes.StatefulIncrementalTaskInputs; -import org.gradle.api.internal.tasks.ContextAwareTaskAction; -import org.gradle.api.internal.tasks.TaskExecutionContext; -import org.gradle.api.tasks.incremental.IncrementalTaskInputs; -import org.gradle.internal.change.Change; -import org.gradle.internal.execution.history.changes.ExecutionStateChanges; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.reflect.Instantiator; -import org.gradle.internal.reflect.JavaMethod; - -import java.lang.reflect.Method; -import java.util.function.Function; -import java.util.function.Supplier; - -class IncrementalTaskAction extends StandardTaskAction implements ContextAwareTaskAction { - - private final Instantiator instantiator; - private TaskExecutionContext context; - - public IncrementalTaskAction(Instantiator instantiator, Class type, Method method) { - super(type, method); - this.instantiator = instantiator; - } - - public void contextualise(TaskExecutionContext context) { - this.context = context; - } - - @Override - public void releaseContext() { - this.context = null; - } - - protected void doExecute(final Task task, String methodName) { - IncrementalTaskInputs incrementalInputs = context.getExecutionStateChanges() - .map(new Function() { - @Override - public StatefulIncrementalTaskInputs apply(ExecutionStateChanges changes) { - return changes.isRebuildRequired() - ? createRebuildInputs(task) - : createIncrementalInputs(changes.getInputFilesChanges()); - } - }).orElseGet(new Supplier() { - @Override - public StatefulIncrementalTaskInputs get() { - return createRebuildInputs(task); - } - }); - - context.setTaskExecutedIncrementally(incrementalInputs.isIncremental()); - JavaMethod.of(task, Object.class, methodName, IncrementalTaskInputs.class).invoke(task, incrementalInputs); - } - - private ChangesOnlyIncrementalTaskInputs createIncrementalInputs(Iterable inputFilesChanges) { - return instantiator.newInstance(ChangesOnlyIncrementalTaskInputs.class, inputFilesChanges); - } - - private RebuildIncrementalTaskInputs createRebuildInputs(Task task) { - ImmutableCollection currentInputs = context.getBeforeExecutionState().get().getInputFileProperties().values(); - return instantiator.newInstance(RebuildIncrementalTaskInputs.class, task, currentInputs); - } -} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalTaskInputsTaskAction.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalTaskInputsTaskAction.java new file mode 100644 index 0000000000000..73f0319bdd199 --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/IncrementalTaskInputsTaskAction.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.project.taskfactory; + +import org.gradle.api.Task; +import org.gradle.api.internal.changedetection.changes.ChangesOnlyIncrementalTaskInputs; +import org.gradle.api.internal.changedetection.changes.RebuildIncrementalTaskInputs; +import org.gradle.api.tasks.incremental.IncrementalTaskInputs; +import org.gradle.api.tasks.incremental.InputFileDetails; +import org.gradle.internal.execution.history.changes.InputChangesInternal; +import org.gradle.internal.reflect.Instantiator; +import org.gradle.internal.reflect.JavaMethod; + +import java.lang.reflect.Method; + +public class IncrementalTaskInputsTaskAction extends AbstractIncrementalTaskAction { + private final Instantiator instantiator; + + public IncrementalTaskInputsTaskAction(Instantiator instantiator, Class type, Method method) { + super(type, method); + this.instantiator = instantiator; + } + + protected void doExecute(Task task, String methodName) { + InputChangesInternal inputChanges = getInputChanges(); + + Iterable allFileChanges = inputChanges.getAllFileChanges(); + IncrementalTaskInputs incrementalTaskInputs = inputChanges.isIncremental() + ? createIncrementalInputs(allFileChanges) + : createRebuildInputs(allFileChanges); + + JavaMethod.of(task, Object.class, methodName, IncrementalTaskInputs.class).invoke(task, incrementalTaskInputs); + } + + private ChangesOnlyIncrementalTaskInputs createIncrementalInputs(Iterable allFileChanges) { + return instantiator.newInstance(ChangesOnlyIncrementalTaskInputs.class, allFileChanges); + } + + private RebuildIncrementalTaskInputs createRebuildInputs(Iterable allFileChanges) { + return instantiator.newInstance(RebuildIncrementalTaskInputs.class, allFileChanges); + } +} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/PropertyAssociationTaskFactory.java b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/PropertyAssociationTaskFactory.java index 52d92cf097843..9f28357639a1e 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/PropertyAssociationTaskFactory.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/project/taskfactory/PropertyAssociationTaskFactory.java @@ -64,7 +64,7 @@ public boolean visitOutputFilePropertiesOnly() { } @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { throw new UnsupportedOperationException(); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/DefaultTaskInputs.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/DefaultTaskInputs.java index 7a7af93ab3ba5..7c5d530137110 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/DefaultTaskInputs.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/DefaultTaskInputs.java @@ -78,6 +78,7 @@ public void visitRegisteredProperties(PropertyVisitor visitor) { registration.getPropertyName(), registration.isOptional(), registration.isSkipWhenEmpty(), + false, registration.getNormalizer(), registration.getValue(), registration.getFilePropertyType()); @@ -212,7 +213,7 @@ public String getDisplayName() { public void visitContents(final FileCollectionResolveContext context) { TaskPropertyUtils.visitProperties(propertyWalker, task, new PropertyVisitor.Adapter() { @Override - public void visitInputFileProperty(final String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(final String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { if (!TaskInputUnionFileCollection.this.skipWhenEmptyOnly || skipWhenEmpty) { FileCollection actualValue = FileParameterUtils.resolveInputFileValue(fileCollectionFactory, filePropertyType, value); context.add(new PropertyFileCollection(task.toString(), propertyName, "input", actualValue)); @@ -230,7 +231,7 @@ public boolean hasInputs() { } @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { hasInputs = true; } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/DefaultTaskOutputCachingState.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/DefaultTaskOutputCachingState.java deleted file mode 100644 index c4dee72781a6e..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/DefaultTaskOutputCachingState.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks; - -import org.gradle.api.internal.TaskOutputCachingState; - -import javax.annotation.Nullable; - -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Strings.isNullOrEmpty; - -public class DefaultTaskOutputCachingState implements TaskOutputCachingState { - public static TaskOutputCachingState enabled() { - return new DefaultTaskOutputCachingState(null, null); - } - public static TaskOutputCachingState disabled(TaskOutputCachingDisabledReasonCategory disabledReasonCategory, String disabledReason) { - checkArgument(!isNullOrEmpty(disabledReason), "disabledReason must be set if task output caching is disabled"); - checkNotNull(disabledReasonCategory, "disabledReasonCategory must be set if task output caching is disabled"); - return new DefaultTaskOutputCachingState(disabledReasonCategory, disabledReason); - } - - private final String disabledReason; - private final TaskOutputCachingDisabledReasonCategory disabledReasonCategory; - - private DefaultTaskOutputCachingState(TaskOutputCachingDisabledReasonCategory disabledReasonCategory, String disabledReason) { - this.disabledReasonCategory = disabledReasonCategory; - this.disabledReason = disabledReason; - } - - @Override - public boolean isEnabled() { - return disabledReason == null; - } - - @Nullable - @Override - public String getDisabledReason() { - return disabledReason; - } - - @Nullable - @Override - public TaskOutputCachingDisabledReasonCategory getDisabledReasonCategory() { - return disabledReasonCategory; - } - - @Override - public String toString() { - return "DefaultTaskOutputCachingState{" - + "disabledReason='" + disabledReason + '\'' - + ", disabledReasonCategory=" + disabledReasonCategory - + '}'; - } -} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/ContextAwareTaskAction.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/InputChangesAwareTaskAction.java similarity index 72% rename from subprojects/core/src/main/java/org/gradle/api/internal/tasks/ContextAwareTaskAction.java rename to subprojects/core/src/main/java/org/gradle/api/internal/tasks/InputChangesAwareTaskAction.java index 01d1cca9c17cf..fa8e96fa5aec5 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/ContextAwareTaskAction.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/InputChangesAwareTaskAction.java @@ -17,8 +17,9 @@ package org.gradle.api.internal.tasks; import org.gradle.api.Describable; +import org.gradle.internal.execution.history.changes.InputChangesInternal; -public interface ContextAwareTaskAction extends ImplementationAwareTaskAction, Describable { - void contextualise(TaskExecutionContext context); - void releaseContext(); +public interface InputChangesAwareTaskAction extends ImplementationAwareTaskAction, Describable { + void setInputChanges(InputChangesInternal inputChanges); + void clearInputChanges(); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/SnapshotTaskInputsBuildOperationResult.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/SnapshotTaskInputsBuildOperationResult.java new file mode 100644 index 0000000000000..84c339f36b342 --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/SnapshotTaskInputsBuildOperationResult.java @@ -0,0 +1,504 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.ImmutableSortedSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.gradle.api.NonNullApi; +import org.gradle.caching.BuildCacheKey; +import org.gradle.internal.execution.caching.CachingInputs; +import org.gradle.internal.execution.caching.CachingState; +import org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.fingerprint.FileSystemLocationFingerprint; +import org.gradle.internal.hash.HashCode; +import org.gradle.internal.operations.trace.CustomOperationTraceSerialization; +import org.gradle.internal.snapshot.DirectorySnapshot; +import org.gradle.internal.snapshot.FileSystemLocationSnapshot; +import org.gradle.internal.snapshot.FileSystemSnapshotVisitor; +import org.gradle.internal.snapshot.impl.ImplementationSnapshot; + +import javax.annotation.Nullable; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Consumer; + +/** + * This operation represents the work of analyzing the task's inputs plus the calculating the cache key. + * + *

+ * These two operations should be captured separately, but for historical reasons we don't yet do that. + * To reproduce this composite operation we capture across executors by starting an operation + * in {@link StartSnapshotTaskInputsBuildOperationTaskExecuter} and finished in {@link MarkSnapshottingInputsFinishedStep}. + *

+ */ +public class SnapshotTaskInputsBuildOperationResult implements SnapshotTaskInputsBuildOperationType.Result, CustomOperationTraceSerialization { + + @VisibleForTesting + final CachingState cachingState; + + public SnapshotTaskInputsBuildOperationResult(CachingState cachingState) { + this.cachingState = cachingState; + } + + @Override + public Map getInputValueHashesBytes() { + return cachingState.getInputs() + .map(new java.util.function.Function>() { + @Nullable + @Override + public Map apply(CachingInputs cachingInputs) { + ImmutableSortedMap inputValueFingerprints = cachingInputs.getInputValueFingerprints(); + if (inputValueFingerprints.isEmpty()) { + return null; + } + return Maps.transformValues(inputValueFingerprints, new Function() { + @Override + public byte[] apply(HashCode input) { + return input.toByteArray(); + } + }); + } + }) + .orElse(null); + } + + @NonNullApi + private static class State implements VisitState, FileSystemSnapshotVisitor { + + final InputFilePropertyVisitor visitor; + + Map fingerprints; + String propertyName; + HashCode propertyHash; + String propertyNormalizationStrategyIdentifier; + String name; + String path; + HashCode hash; + int depth; + + public State(InputFilePropertyVisitor visitor) { + this.visitor = visitor; + } + + @Override + public String getPropertyName() { + return propertyName; + } + + @Override + public byte[] getPropertyHashBytes() { + return propertyHash.toByteArray(); + } + + @Override + public String getPropertyNormalizationStrategyName() { + return propertyNormalizationStrategyIdentifier; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getPath() { + return path; + } + + @Override + public byte[] getHashBytes() { + return hash.toByteArray(); + } + + @Override + public boolean preVisitDirectory(DirectorySnapshot physicalSnapshot) { + this.path = physicalSnapshot.getAbsolutePath(); + this.name = physicalSnapshot.getName(); + this.hash = null; + + if (depth++ == 0) { + visitor.preRoot(this); + } + + visitor.preDirectory(this); + + return true; + } + + @Override + public void visit(FileSystemLocationSnapshot snapshot) { + this.path = snapshot.getAbsolutePath(); + this.name = snapshot.getName(); + + FileSystemLocationFingerprint fingerprint = fingerprints.get(path); + if (fingerprint == null) { + return; + } + + this.hash = fingerprint.getNormalizedContentHash(); + + boolean isRoot = depth == 0; + if (isRoot) { + visitor.preRoot(this); + } + + visitor.file(this); + + if (isRoot) { + visitor.postRoot(); + } + } + + @Override + public void postVisitDirectory(DirectorySnapshot directorySnapshot) { + visitor.postDirectory(); + if (--depth == 0) { + visitor.postRoot(); + } + } + } + + @Override + public void visitInputFileProperties(final InputFilePropertyVisitor visitor) { + cachingState.getInputs().ifPresent(new Consumer() { + @Override + public void accept(CachingInputs inputs) { + State state = new State(visitor); + for (Map.Entry entry : inputs.getInputFileFingerprints().entrySet()) { + CurrentFileCollectionFingerprint fingerprint = entry.getValue(); + + state.propertyName = entry.getKey(); + state.propertyHash = fingerprint.getHash(); + state.propertyNormalizationStrategyIdentifier = fingerprint.getStrategyIdentifier(); + state.fingerprints = fingerprint.getFingerprints(); + + visitor.preProperty(state); + fingerprint.accept(state); + visitor.postProperty(); + } + } + }); + } + + @Nullable + @Override + public Set getInputPropertiesLoadedByUnknownClassLoader() { + return cachingState.getInputs() + .map(new java.util.function.Function>() { + @Nullable + @Override + public Set apply(CachingInputs inputs) { + ImmutableSortedSet invalidInputProperties = inputs.getNonCacheableInputProperties(); + if (invalidInputProperties.isEmpty()) { + return null; + } + return invalidInputProperties; + } + }) + .orElse(null); + } + + + @Override + public byte[] getClassLoaderHashBytes() { + return cachingState.getInputs() + .map(new java.util.function.Function() { + @Nullable + @Override + public byte[] apply(CachingInputs inputs) { + ImplementationSnapshot implementation = inputs.getImplementation(); + if (implementation.getClassLoaderHash() == null) { + return null; + } + return implementation.getClassLoaderHash().toByteArray(); + } + }) + .orElse(null); + } + + @Override + public List getActionClassLoaderHashesBytes() { + return cachingState.getInputs() + .map(new java.util.function.Function>() { + @Nullable + @Override + public List apply(CachingInputs inputs) { + List additionalImplementations = inputs.getAdditionalImplementations(); + if (additionalImplementations.isEmpty()) { + return null; + } + return Lists.transform(additionalImplementations, new Function() { + @Override + public byte[] apply(ImplementationSnapshot input) { + return input.getClassLoaderHash() == null ? null : input.getClassLoaderHash().toByteArray(); + } + }); + } + }) + .orElse(null); + } + + @Nullable + @Override + public List getActionClassNames() { + return cachingState.getInputs() + .map(new java.util.function.Function>() { + @Nullable + @Override + public List apply(CachingInputs inputs) { + List additionalImplementations = inputs.getAdditionalImplementations(); + if (additionalImplementations.isEmpty()) { + return null; + } + return Lists.transform(additionalImplementations, new Function() { + @Override + public String apply(ImplementationSnapshot input) { + return input.getTypeName(); + } + }); + } + }) + .orElse(null); + } + + @Nullable + @Override + public List getOutputPropertyNames() { + return cachingState.getInputs() + .map(new java.util.function.Function>() { + @Nullable + @Override + public List apply(CachingInputs inputs) { + ImmutableSortedSet outputPropertyNames = inputs.getOutputProperties(); + if (outputPropertyNames.isEmpty()) { + return null; + } + return outputPropertyNames.asList(); + } + }) + .orElse(null); + } + + @Override + public byte[] getHashBytes() { + return cachingState.getKey() + .map(new java.util.function.Function() { + @Override + public byte[] apply(BuildCacheKey cacheKey) { + return cacheKey.toByteArray(); + } + }) + .orElse(null); + } + + @Override + public Object getCustomOperationTraceSerializableModel() { + Map model = new TreeMap(); + + final Function bytesToString = new Function() { + @Nullable + @Override + public String apply(@Nullable byte[] input) { + if (input == null) { + return null; + } + return HashCode.fromBytes(input).toString(); + } + }; + + List actionClassLoaderHashesBytes = getActionClassLoaderHashesBytes(); + if (actionClassLoaderHashesBytes != null) { + model.put("actionClassLoaderHashes", Lists.transform(getActionClassLoaderHashesBytes(), bytesToString)); + } else { + model.put("actionClassLoaderHashes", null); + } + + model.put("actionClassNames", getActionClassNames()); + + byte[] hashBytes = getHashBytes(); + if (hashBytes != null) { + model.put("hash", HashCode.fromBytes(hashBytes).toString()); + } else { + model.put("hash", null); + } + + byte[] classLoaderHashBytes = getClassLoaderHashBytes(); + if (classLoaderHashBytes != null) { + model.put("classLoaderHash", HashCode.fromBytes(classLoaderHashBytes).toString()); + } else { + model.put("classLoaderHash", null); + } + + + model.put("inputFileProperties", fileProperties()); + + model.put("inputPropertiesLoadedByUnknownClassLoader", getInputPropertiesLoadedByUnknownClassLoader()); + + Map inputValueHashesBytes = getInputValueHashesBytes(); + if (inputValueHashesBytes != null) { + model.put("inputValueHashes", Maps.transformEntries(inputValueHashesBytes, new Maps.EntryTransformer() { + @Nullable + @Override + public String transformEntry(@Nullable String key, @Nullable byte[] value) { + if (value == null) { + return null; + } + return HashCode.fromBytes(value).toString(); + } + })); + } else { + model.put("inputValueHashes", null); + } + + model.put("outputPropertyNames", getOutputPropertyNames()); + + return model; + } + + protected Map fileProperties() { + final Map fileProperties = new TreeMap(); + visitInputFileProperties(new InputFilePropertyVisitor() { + Property property; + Deque dirStack = new ArrayDeque(); + + class Property { + private final String hash; + private final String normalization; + private final List roots = new ArrayList(); + + public Property(String hash, String normalization) { + this.hash = hash; + this.normalization = normalization; + } + + public String getHash() { + return hash; + } + + public String getNormalization() { + return normalization; + } + + public Collection getRoots() { + return roots; + } + } + + abstract class Entry { + private final String path; + + public Entry(String path) { + this.path = path; + } + + public String getPath() { + return path; + } + + } + + class FileEntry extends Entry { + private final String hash; + + FileEntry(String path, String hash) { + super(path); + this.hash = hash; + } + + public String getHash() { + return hash; + } + } + + class DirEntry extends Entry { + private final List children = new ArrayList(); + + DirEntry(String path) { + super(path); + } + + public Collection getChildren() { + return children; + } + } + + @Override + public void preProperty(VisitState state) { + property = new Property(HashCode.fromBytes(state.getPropertyHashBytes()).toString(), state.getPropertyNormalizationStrategyName()); + fileProperties.put(state.getPropertyName(), property); + } + + @Override + public void preRoot(VisitState state) { + + } + + @Override + public void preDirectory(VisitState state) { + boolean isRoot = dirStack.isEmpty(); + DirEntry dir = new DirEntry(isRoot ? state.getPath() : state.getName()); + if (isRoot) { + property.roots.add(dir); + } else { + //noinspection ConstantConditions + dirStack.peek().children.add(dir); + } + dirStack.push(dir); + } + + @Override + public void file(VisitState state) { + boolean isRoot = dirStack.isEmpty(); + FileEntry file = new FileEntry(isRoot ? state.getPath() : state.getName(), HashCode.fromBytes(state.getHashBytes()).toString()); + if (isRoot) { + property.roots.add(file); + } else { + //noinspection ConstantConditions + dirStack.peek().children.add(file); + } + } + + @Override + public void postDirectory() { + dirStack.pop(); + } + + @Override + public void postRoot() { + + } + + @Override + public void postProperty() { + + } + }); + return fileProperties; + } + +} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecuterResult.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecuterResult.java index c723ef40f5c25..1505baf2fc19f 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecuterResult.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecuterResult.java @@ -16,20 +16,53 @@ package org.gradle.api.internal.tasks; +import com.google.common.collect.ImmutableList; import org.gradle.caching.internal.origin.OriginMetadata; +import org.gradle.internal.execution.caching.CachingState; +import java.util.List; import java.util.Optional; public interface TaskExecuterResult { + /** + * Returns the reasons for executing this task. An empty list means the task was not executed. + */ + List getExecutionReasons(); + + /** + * Whether the task was executed incrementally. + */ + boolean executedIncrementally(); + /** * If the execution resulted in some previous output being reused, this returns its origin metadata. */ Optional getReusedOutputOriginMetadata(); - TaskExecuterResult NO_REUSED_OUTPUT = new TaskExecuterResult() { + /** + * The caching state of the task, including all its captured inputs and the cache key if calculated. + */ + CachingState getCachingState(); + + TaskExecuterResult WITHOUT_OUTPUTS = new TaskExecuterResult() { + @Override + public List getExecutionReasons() { + return ImmutableList.of(); + } + + @Override + public boolean executedIncrementally() { + return false; + } + @Override public Optional getReusedOutputOriginMetadata() { return Optional.empty(); } + + @Override + public CachingState getCachingState() { + return CachingState.NOT_DETERMINED; + } }; } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecutionContext.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecutionContext.java index df58620fa1158..cbaf7692c2988 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecutionContext.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecutionContext.java @@ -20,17 +20,13 @@ import org.gradle.api.internal.OverlappingOutputs; import org.gradle.api.internal.changedetection.TaskExecutionMode; import org.gradle.api.internal.tasks.properties.TaskProperties; -import org.gradle.api.tasks.incremental.IncrementalTaskInputs; -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey; import org.gradle.execution.plan.LocalTaskNode; import org.gradle.internal.execution.history.AfterPreviousExecutionState; import org.gradle.internal.execution.history.BeforeExecutionState; -import org.gradle.internal.execution.history.changes.ExecutionStateChanges; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; import org.gradle.internal.operations.ExecutingBuildOperation; import javax.annotation.Nullable; -import java.util.List; import java.util.Optional; public interface TaskExecutionContext { @@ -54,10 +50,6 @@ public interface TaskExecutionContext { void setOutputFilesBeforeExecution(ImmutableSortedMap outputFilesBeforeExecution); - TaskOutputCachingBuildCacheKey getBuildCacheKey(); - - void setBuildCacheKey(TaskOutputCachingBuildCacheKey cacheKey); - /** * Sets the execution time of the task to be the elapsed time since start to now. * @@ -71,11 +63,6 @@ public interface TaskExecutionContext { */ long markExecutionTime(); - @Nullable - List getUpToDateMessages(); - - void setUpToDateMessages(List upToDateMessages); - void setTaskProperties(TaskProperties properties); TaskProperties getTaskProperties(); @@ -87,23 +74,6 @@ public interface TaskExecutionContext { void setTaskCachingEnabled(boolean enabled); - /** - * Returns if this task was executed incrementally. - * - * @see IncrementalTaskInputs#isIncremental() - */ - boolean isTaskExecutedIncrementally(); - - void setTaskExecutedIncrementally(boolean taskExecutedIncrementally); - - boolean isOutputRemovedBeforeExecution(); - - void setOutputRemovedBeforeExecution(boolean outputRemovedBeforeExecution); - - Optional getExecutionStateChanges(); - - void setExecutionStateChanges(ExecutionStateChanges executionStateChanges); - Optional getOverlappingOutputs(); void setOverlappingOutputs(OverlappingOutputs overlappingOutputs); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecutionOutcome.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecutionOutcome.java index 673e6809a752b..fb0a3de339bf8 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecutionOutcome.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskExecutionOutcome.java @@ -56,7 +56,8 @@ public static TaskExecutionOutcome valueOf(ExecutionOutcome outcome) { return FROM_CACHE; case UP_TO_DATE: return UP_TO_DATE; - case EXECUTED: + case EXECUTED_INCREMENTALLY: + case EXECUTED_NON_INCREMENTALLY: return EXECUTED; default: throw new AssertionError(); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskPropertyUtils.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskPropertyUtils.java index 45a5219f6941a..b5f141403b416 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskPropertyUtils.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskPropertyUtils.java @@ -16,21 +16,31 @@ package org.gradle.api.internal.tasks; +import org.gradle.api.InvalidUserDataException; import org.gradle.api.NonNullApi; +import org.gradle.api.Transformer; import org.gradle.api.internal.TaskInternal; -import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.api.internal.tasks.properties.PropertyVisitor; import org.gradle.api.internal.tasks.properties.PropertyWalker; +import org.gradle.api.tasks.TaskValidationException; +import org.gradle.internal.reflect.ParameterValidationContext; +import org.gradle.util.CollectionUtils; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; @NonNullApi public class TaskPropertyUtils { - /** * Visits both properties declared via annotations on the properties of the task type as well as * properties declared via the runtime API ({@link org.gradle.api.tasks.TaskInputs} etc.). */ - public static void visitProperties(PropertyWalker propertyWalker, final TaskInternal task, PropertyVisitor visitor) { - propertyWalker.visitProperties(task, ParameterValidationContext.NOOP, visitor); + public static void visitProperties(PropertyWalker propertyWalker, final TaskInternal task, final PropertyVisitor visitor) { + StrictErrorsOnlyContext validationContext = new StrictErrorsOnlyContext(task); + propertyWalker.visitProperties(task, validationContext, visitor); + // Should instead forward these to the task's validation context + validationContext.assertNoProblems(); if (!visitor.visitOutputFilePropertiesOnly()) { task.getInputs().visitRegisteredProperties(visitor); } @@ -57,4 +67,49 @@ public static String checkPropertyName(String propertyName) { } return propertyName; } + + private static class StrictErrorsOnlyContext implements ParameterValidationContext { + private final TaskInternal task; + List problems; + + public StrictErrorsOnlyContext(TaskInternal task) { + this.task = task; + } + + void assertNoProblems() { + if (problems == null) { + return; + } + String message; + if (problems.size() == 1) { + message = String.format("A problem was found with the configuration of %s.", task); + } else { + message = String.format("Some problems were found with the configuration of %s.", task); + } + throw new TaskValidationException(message, CollectionUtils.collect(problems, new Transformer() { + @Override + public InvalidUserDataException transform(String message) { + return new InvalidUserDataException(message); + } + })); + } + + @Override + public void visitError(@Nullable String ownerPath, String propertyName, String message) { + // Ignore for now + } + + @Override + public void visitError(String message) { + // Ignore for now + } + + @Override + public void visitErrorStrict(String message) { + if (problems == null) { + problems = new ArrayList(); + } + problems.add(message); + } + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskStateInternal.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskStateInternal.java index 33a618130d656..fb2dd41b37cc9 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskStateInternal.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/TaskStateInternal.java @@ -16,7 +16,6 @@ package org.gradle.api.internal.tasks; -import org.gradle.api.internal.TaskOutputCachingState; import org.gradle.api.tasks.TaskExecutionException; import org.gradle.api.tasks.TaskState; import org.gradle.util.CollectionUtils; @@ -30,7 +29,6 @@ public class TaskStateInternal implements TaskState { private boolean actionable = true; private boolean didWork; private RuntimeException failure; - private TaskOutputCachingState taskOutputCaching = DefaultTaskOutputCachingState.disabled(TaskOutputCachingDisabledReasonCategory.UNKNOWN, "Cacheability was not determined"); private TaskExecutionOutcome outcome; public boolean getDidWork() { @@ -93,14 +91,6 @@ public void setExecuting(boolean executing) { this.executing = executing; } - public void setTaskOutputCaching(TaskOutputCachingState taskOutputCaching) { - this.taskOutputCaching = taskOutputCaching; - } - - public TaskOutputCachingState getTaskOutputCaching() { - return taskOutputCaching; - } - public Throwable getFailure() { return failure; } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/CatchExceptionTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/CatchExceptionTaskExecuter.java index 03adf2cf04fce..a85bf2f6b7ee4 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/CatchExceptionTaskExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/CatchExceptionTaskExecuter.java @@ -36,7 +36,7 @@ public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, Ta return delegate.execute(task, state, context); } catch (RuntimeException e) { state.setOutcome(new TaskExecutionException(task, e)); - return TaskExecuterResult.NO_REUSED_OUTPUT; + return TaskExecuterResult.WITHOUT_OUTPUTS; } } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/CleanupStaleOutputsExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/CleanupStaleOutputsExecuter.java index d0b73af37e764..85262c3fb7f04 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/CleanupStaleOutputsExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/CleanupStaleOutputsExecuter.java @@ -24,8 +24,6 @@ import org.gradle.api.internal.tasks.TaskStateInternal; import org.gradle.api.internal.tasks.properties.FilePropertySpec; import org.gradle.api.internal.tasks.properties.TaskProperties; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.cleanup.BuildOutputCleanupRegistry; import org.gradle.internal.execution.OutputChangeListener; import org.gradle.internal.execution.history.OutputFilesRepository; @@ -34,6 +32,8 @@ import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.RunnableBuildOperation; import org.gradle.util.GFileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.util.HashSet; @@ -43,7 +43,7 @@ public class CleanupStaleOutputsExecuter implements TaskExecuter { public static final String CLEAN_STALE_OUTPUTS_DISPLAY_NAME = "Clean stale outputs"; - private final Logger logger = Logging.getLogger(CleanupStaleOutputsExecuter.class); + private final Logger logger = LoggerFactory.getLogger(CleanupStaleOutputsExecuter.class); private final BuildOperationExecutor buildOperationExecutor; private final OutputChangeListener outputChangeListener; private final TaskExecuter executer; diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/DefaultTaskCacheabilityResolver.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/DefaultTaskCacheabilityResolver.java new file mode 100644 index 0000000000000..f7f0374215bdd --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/DefaultTaskCacheabilityResolver.java @@ -0,0 +1,94 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.execution; + +import com.google.common.collect.ImmutableSortedSet; +import org.gradle.api.internal.OverlappingOutputs; +import org.gradle.api.internal.TaskInternal; +import org.gradle.api.internal.tasks.properties.CacheableOutputFilePropertySpec; +import org.gradle.api.internal.tasks.properties.OutputFilePropertySpec; +import org.gradle.internal.execution.caching.CachingDisabledReason; +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory; +import org.gradle.internal.file.RelativeFilePathResolver; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Optional; + +public class DefaultTaskCacheabilityResolver implements TaskCacheabilityResolver { + private static final CachingDisabledReason CACHING_NOT_ENABLED = new CachingDisabledReason(CachingDisabledReasonCategory.NOT_CACHEABLE, "Caching has not been enabled for the task"); + private static final CachingDisabledReason NO_OUTPUTS_DECLARED = new CachingDisabledReason(CachingDisabledReasonCategory.NO_OUTPUTS_DECLARED, "No outputs declared"); + + private final RelativeFilePathResolver relativeFilePathResolver; + + public DefaultTaskCacheabilityResolver(RelativeFilePathResolver relativeFilePathResolver) { + this.relativeFilePathResolver = relativeFilePathResolver; + } + + @Override + public Optional shouldDisableCaching( + boolean hasDeclaredOutputs, + ImmutableSortedSet outputFileProperties, + TaskInternal task, + Collection> cacheIfSpecs, + Collection> doNotCacheIfSpecs, + @Nullable OverlappingOutputs overlappingOutputs + ) { + if (cacheIfSpecs.isEmpty()) { + return Optional.of(CACHING_NOT_ENABLED); + } + + if (!hasDeclaredOutputs) { + return Optional.of(NO_OUTPUTS_DECLARED); + } + + if (overlappingOutputs != null) { + String relativePath = relativeFilePathResolver.resolveAsRelativePath(overlappingOutputs.getOverlappedFilePath()); + return Optional.of(new CachingDisabledReason(CachingDisabledReasonCategory.OVERLAPPING_OUTPUTS, + "Gradle does not know how file '" + relativePath + "' was created (output property '" + overlappingOutputs.getPropertyName() + "'). Task output caching requires exclusive access to output paths to guarantee correctness.")); + } + + for (OutputFilePropertySpec spec : outputFileProperties) { + if (!(spec instanceof CacheableOutputFilePropertySpec)) { + return Optional.of(new CachingDisabledReason( + CachingDisabledReasonCategory.NON_CACHEABLE_OUTPUT, + "Output property '" + spec.getPropertyName() + "' contains a file tree" + )); + } + } + + for (SelfDescribingSpec cacheIfSpec : cacheIfSpecs) { + if (!cacheIfSpec.isSatisfiedBy(task)) { + return Optional.of(new CachingDisabledReason( + CachingDisabledReasonCategory.ENABLE_CONDITION_NOT_SATISFIED, + "'" + cacheIfSpec.getDisplayName() + "' not satisfied" + )); + } + } + + for (SelfDescribingSpec doNotCacheIfSpec : doNotCacheIfSpecs) { + if (doNotCacheIfSpec.isSatisfiedBy(task)) { + return Optional.of(new CachingDisabledReason( + CachingDisabledReasonCategory.DISABLE_CONDITION_SATISFIED, + "'" + doNotCacheIfSpec.getDisplayName() + "' satisfied" + )); + } + } + + return Optional.empty(); + } +} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/DefaultTaskExecutionContext.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/DefaultTaskExecutionContext.java index ea006868e7d08..49aad1d9d53d1 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/DefaultTaskExecutionContext.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/DefaultTaskExecutionContext.java @@ -20,18 +20,15 @@ import org.gradle.api.internal.changedetection.TaskExecutionMode; import org.gradle.api.internal.tasks.TaskExecutionContext; import org.gradle.api.internal.tasks.properties.TaskProperties; -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey; import org.gradle.execution.plan.LocalTaskNode; import org.gradle.internal.execution.history.AfterPreviousExecutionState; import org.gradle.internal.execution.history.BeforeExecutionState; -import org.gradle.internal.execution.history.changes.ExecutionStateChanges; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; import org.gradle.internal.operations.ExecutingBuildOperation; import org.gradle.internal.time.Time; import org.gradle.internal.time.Timer; import javax.annotation.Nullable; -import java.util.List; import java.util.Optional; public class DefaultTaskExecutionContext implements TaskExecutionContext { @@ -39,20 +36,15 @@ public class DefaultTaskExecutionContext implements TaskExecutionContext { private final LocalTaskNode localTaskNode; private AfterPreviousExecutionState afterPreviousExecution; private OverlappingOutputs overlappingOutputs; - private ExecutionStateChanges executionStateChanges; private ImmutableSortedMap outputFilesBeforeExecution; private BeforeExecutionState beforeExecutionState; private TaskExecutionMode taskExecutionMode; - private boolean outputRemovedBeforeExecution; - private TaskOutputCachingBuildCacheKey buildCacheKey; - private List upToDateMessages; private TaskProperties properties; private boolean taskCachingEnabled; private Long executionTime; private ExecutingBuildOperation snapshotTaskInputsBuildOperation; private final Timer executionTimer; - private boolean taskExecutedIncrementally; public DefaultTaskExecutionContext(LocalTaskNode localTaskNode) { this.localTaskNode = localTaskNode; @@ -114,36 +106,6 @@ public void setTaskExecutionMode(TaskExecutionMode taskExecutionMode) { this.taskExecutionMode = taskExecutionMode; } - @Override - public boolean isOutputRemovedBeforeExecution() { - return outputRemovedBeforeExecution; - } - - @Override - public void setOutputRemovedBeforeExecution(boolean outputRemovedBeforeExecution) { - this.outputRemovedBeforeExecution = outputRemovedBeforeExecution; - } - - @Override - public Optional getExecutionStateChanges() { - return Optional.ofNullable(executionStateChanges); - } - - @Override - public void setExecutionStateChanges(ExecutionStateChanges executionStateChanges) { - this.executionStateChanges = executionStateChanges; - } - - @Override - public TaskOutputCachingBuildCacheKey getBuildCacheKey() { - return buildCacheKey; - } - - @Override - public void setBuildCacheKey(TaskOutputCachingBuildCacheKey buildCacheKey) { - this.buildCacheKey = buildCacheKey; - } - public long markExecutionTime() { if (this.executionTime != null) { throw new IllegalStateException("execution time already set"); @@ -152,17 +114,6 @@ public long markExecutionTime() { return this.executionTime = executionTimer.getElapsedMillis(); } - @Override - @Nullable - public List getUpToDateMessages() { - return upToDateMessages; - } - - @Override - public void setUpToDateMessages(List upToDateMessages) { - this.upToDateMessages = upToDateMessages; - } - @Override public void setTaskProperties(TaskProperties properties) { this.properties = properties; @@ -183,16 +134,6 @@ public void setTaskCachingEnabled(boolean taskCachingEnabled) { this.taskCachingEnabled = taskCachingEnabled; } - @Override - public boolean isTaskExecutedIncrementally() { - return taskExecutedIncrementally; - } - - @Override - public void setTaskExecutedIncrementally(boolean taskExecutedIncrementally) { - this.taskExecutedIncrementally = taskExecutedIncrementally; - } - @Override public Optional removeSnapshotTaskInputsBuildOperation() { Optional result = Optional.ofNullable(snapshotTaskInputsBuildOperation); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/EventFiringTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/EventFiringTaskExecuter.java index 0a3ef92644366..8f884c1875bba 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/EventFiringTaskExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/EventFiringTaskExecuter.java @@ -22,11 +22,14 @@ import org.gradle.api.internal.tasks.TaskExecuterResult; import org.gradle.api.internal.tasks.TaskExecutionContext; import org.gradle.api.internal.tasks.TaskStateInternal; +import org.gradle.api.logging.Logger; import org.gradle.api.tasks.TaskExecutionException; +import org.gradle.internal.logging.slf4j.ContextAwareTaskLogger; import org.gradle.internal.operations.BuildOperationCategory; import org.gradle.internal.operations.BuildOperationContext; import org.gradle.internal.operations.BuildOperationDescriptor; import org.gradle.internal.operations.BuildOperationExecutor; +import org.gradle.internal.operations.BuildOperationRef; import org.gradle.internal.operations.CallableBuildOperation; public class EventFiringTaskExecuter implements TaskExecuter { @@ -53,15 +56,32 @@ public TaskExecuterResult call(BuildOperationContext operationContext) { } private TaskExecuterResult executeTask(BuildOperationContext operationContext) { + Logger logger = task.getLogger(); + ContextAwareTaskLogger contextAwareTaskLogger = null; try { taskExecutionListener.beforeExecute(task); + BuildOperationRef currentOperation = buildOperationExecutor.getCurrentOperation(); + if (logger instanceof ContextAwareTaskLogger) { + contextAwareTaskLogger = (ContextAwareTaskLogger) logger; + contextAwareTaskLogger.setFallbackBuildOperationId(currentOperation.getId()); + } } catch (Throwable t) { state.setOutcome(new TaskExecutionException(task, t)); - return TaskExecuterResult.NO_REUSED_OUTPUT; + return TaskExecuterResult.WITHOUT_OUTPUTS; } TaskExecuterResult result = delegate.execute(task, state, context); - operationContext.setResult(new ExecuteTaskBuildOperationResult(state, context, result.getReusedOutputOriginMetadata().orElse(null))); + + if (contextAwareTaskLogger != null) { + contextAwareTaskLogger.setFallbackBuildOperationId(null); + } + operationContext.setResult(new ExecuteTaskBuildOperationResult( + state, + result.getCachingState(), + result.getReusedOutputOriginMetadata().orElse(null), + result.executedIncrementally(), + result.getExecutionReasons() + )); try { taskExecutionListener.afterExecute(task, state); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java index 3ae6d2acf32fe..cb628d941f422 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuter.java @@ -16,51 +16,56 @@ package org.gradle.api.internal.tasks.execution; import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Lists; import org.gradle.api.execution.TaskActionListener; import org.gradle.api.internal.OverlappingOutputs; import org.gradle.api.internal.TaskInternal; -import org.gradle.api.internal.tasks.ContextAwareTaskAction; +import org.gradle.api.internal.project.taskfactory.AbstractIncrementalTaskAction; +import org.gradle.api.internal.project.taskfactory.IncrementalTaskInputsTaskAction; +import org.gradle.api.internal.tasks.InputChangesAwareTaskAction; +import org.gradle.api.internal.tasks.SnapshotTaskInputsBuildOperationResult; import org.gradle.api.internal.tasks.TaskExecuter; import org.gradle.api.internal.tasks.TaskExecuterResult; import org.gradle.api.internal.tasks.TaskExecutionContext; import org.gradle.api.internal.tasks.TaskExecutionOutcome; import org.gradle.api.internal.tasks.TaskStateInternal; import org.gradle.api.internal.tasks.properties.CacheableOutputFilePropertySpec; +import org.gradle.api.internal.tasks.properties.InputFilePropertySpec; import org.gradle.api.internal.tasks.properties.OutputFilePropertySpec; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.api.tasks.StopActionException; import org.gradle.api.tasks.StopExecutionException; import org.gradle.api.tasks.TaskExecutionException; -import org.gradle.caching.BuildCacheKey; import org.gradle.caching.internal.origin.OriginMetadata; import org.gradle.internal.UncheckedException; import org.gradle.internal.exceptions.Contextual; import org.gradle.internal.exceptions.DefaultMultiCauseException; import org.gradle.internal.exceptions.MultiCauseException; -import org.gradle.internal.execution.CacheHandler; -import org.gradle.internal.execution.ExecutionException; +import org.gradle.internal.execution.CachingResult; import org.gradle.internal.execution.ExecutionOutcome; +import org.gradle.internal.execution.IncrementalContext; import org.gradle.internal.execution.UnitOfWork; import org.gradle.internal.execution.WorkExecutor; +import org.gradle.internal.execution.caching.CachingDisabledReason; +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory; +import org.gradle.internal.execution.caching.CachingState; import org.gradle.internal.execution.history.AfterPreviousExecutionState; import org.gradle.internal.execution.history.BeforeExecutionState; import org.gradle.internal.execution.history.ExecutionHistoryStore; -import org.gradle.internal.execution.history.OutputFilesRepository; -import org.gradle.internal.execution.history.changes.ExecutionStateChanges; -import org.gradle.internal.execution.history.changes.OutputFileChanges; +import org.gradle.internal.execution.history.changes.InputChangesInternal; import org.gradle.internal.execution.impl.OutputFilterUtil; -import org.gradle.internal.execution.impl.steps.UpToDateResult; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.fingerprint.FileCollectionFingerprint; import org.gradle.internal.operations.BuildOperationContext; import org.gradle.internal.operations.BuildOperationDescriptor; import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.BuildOperationRef; +import org.gradle.internal.operations.ExecutingBuildOperation; import org.gradle.internal.operations.RunnableBuildOperation; import org.gradle.internal.work.AsyncWorkTracker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; import java.io.File; import java.time.Duration; import java.util.ArrayList; @@ -73,40 +78,66 @@ * A {@link TaskExecuter} which executes the actions of a task. */ public class ExecuteActionsTaskExecuter implements TaskExecuter { - private static final Logger LOGGER = Logging.getLogger(ExecuteActionsTaskExecuter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ExecuteActionsTaskExecuter.class); + + private static final CachingDisabledReason NO_OUTPUTS_DECLARED = new CachingDisabledReason(CachingDisabledReasonCategory.NO_OUTPUTS_DECLARED, "No outputs declared"); private final boolean buildCacheEnabled; + private final boolean scanPluginApplied; private final TaskFingerprinter taskFingerprinter; private final ExecutionHistoryStore executionHistoryStore; - private final OutputFilesRepository outputFilesRepository; private final BuildOperationExecutor buildOperationExecutor; private final AsyncWorkTracker asyncWorkTracker; private final TaskActionListener actionListener; - private final WorkExecutor workExecutor; + private final TaskCacheabilityResolver taskCacheabilityResolver; + private final WorkExecutor workExecutor; public ExecuteActionsTaskExecuter( boolean buildCacheEnabled, + boolean scanPluginApplied, TaskFingerprinter taskFingerprinter, ExecutionHistoryStore executionHistoryStore, - OutputFilesRepository outputFilesRepository, BuildOperationExecutor buildOperationExecutor, AsyncWorkTracker asyncWorkTracker, TaskActionListener actionListener, - WorkExecutor workExecutor + TaskCacheabilityResolver taskCacheabilityResolver, + WorkExecutor workExecutor ) { this.buildCacheEnabled = buildCacheEnabled; + this.scanPluginApplied = scanPluginApplied; this.taskFingerprinter = taskFingerprinter; this.executionHistoryStore = executionHistoryStore; - this.outputFilesRepository = outputFilesRepository; this.buildOperationExecutor = buildOperationExecutor; this.asyncWorkTracker = asyncWorkTracker; this.actionListener = actionListener; + this.taskCacheabilityResolver = taskCacheabilityResolver; this.workExecutor = workExecutor; } @Override - public TaskExecuterResult execute(final TaskInternal task, final TaskStateInternal state, TaskExecutionContext context) { - final UpToDateResult result = workExecutor.execute(new TaskExecution(task, context)); + public TaskExecuterResult execute(final TaskInternal task, final TaskStateInternal state, final TaskExecutionContext context) { + final TaskExecution work = new TaskExecution(task, context, executionHistoryStore); + final CachingResult result = workExecutor.execute(new IncrementalContext() { + @Override + public UnitOfWork getWork() { + return work; + } + + @Override + public Optional getRebuildReason() { + return context.getTaskExecutionMode().getRebuildReason(); + } + + @Override + public Optional getAfterPreviousExecutionState() { + return Optional.ofNullable(context.getAfterPreviousExecution()); + } + + @Override + public Optional getBeforeExecutionState() { + return context.getBeforeExecutionState(); + } + }); result.getOutcome().ifSuccessfulOrElse( new Consumer() { @Override @@ -117,31 +148,55 @@ public void accept(ExecutionOutcome outcome) { new Consumer() { @Override public void accept(Throwable failure) { - state.setOutcome(failure instanceof ExecutionException - ? new TaskExecutionException(task, failure.getCause()) - : new TaskExecutionException(task, failure)); + state.setOutcome(new TaskExecutionException(task, failure)); } } ); - context.setUpToDateMessages(result.getOutOfDateReasons()); return new TaskExecuterResult() { @Override public Optional getReusedOutputOriginMetadata() { - //noinspection RedundantTypeArguments return result.isReused() ? Optional.of(result.getOriginMetadata()) : Optional.empty(); } + + @Override + public boolean executedIncrementally() { + return result.getOutcome() + .map(new Function() { + @Override + public Boolean apply(ExecutionOutcome executionOutcome) { + return executionOutcome == ExecutionOutcome.EXECUTED_INCREMENTALLY; + } + }).orElseMapFailure(new Function() { + @Override + public Boolean apply(Throwable throwable) { + return false; + } + }); + } + + @Override + public List getExecutionReasons() { + return result.getExecutionReasons(); + } + + @Override + public CachingState getCachingState() { + return result.getCachingState(); + } }; } private class TaskExecution implements UnitOfWork { private final TaskInternal task; private final TaskExecutionContext context; + private final ExecutionHistoryStore executionHistoryStore; - public TaskExecution(TaskInternal task, TaskExecutionContext context) { + public TaskExecution(TaskInternal task, TaskExecutionContext context, ExecutionHistoryStore executionHistoryStore) { this.task = task; this.context = context; + this.executionHistoryStore = executionHistoryStore; } @Override @@ -150,21 +205,24 @@ public String getIdentity() { } @Override - public ExecutionOutcome execute() { + public WorkResult execute(@Nullable InputChangesInternal inputChanges) { task.getState().setExecuting(true); try { LOGGER.debug("Executing actions for {}.", task); actionListener.beforeActions(task); - executeActions(task, context); - return task.getState().getDidWork() - ? ExecutionOutcome.EXECUTED - : ExecutionOutcome.UP_TO_DATE; + executeActions(task, inputChanges); + return task.getState().getDidWork() ? WorkResult.DID_WORK : WorkResult.DID_NO_WORK; } finally { task.getState().setExecuting(false); actionListener.afterActions(task); } } + @Override + public ExecutionHistoryStore getExecutionHistoryStore() { + return executionHistoryStore; + } + @Override public void visitOutputProperties(OutputPropertyVisitor visitor) { for (OutputFilePropertySpec property : context.getTaskProperties().getOutputFileProperties()) { @@ -195,8 +253,8 @@ public void visitLocalState(LocalStateVisitor visitor) { } @Override - public Optional getChangesSincePreviousExecution() { - return context.getExecutionStateChanges(); + public boolean isAllowOverlappingOutputs() { + return true; } @Override @@ -205,39 +263,24 @@ public Optional> getChangingOutputs() { } @Override - public CacheHandler createCacheHandler() { - return new CacheHandler() { - @Override - public Optional load(Function loader) { - // TODO Log this when creating the build cache key perhaps? - if (task.isHasCustomActions()) { - LOGGER.info("Custom actions are attached to {}.", task); - } - if (context.isTaskCachingEnabled() - && context.getTaskExecutionMode().isAllowedToUseCachedResults() - && context.getBuildCacheKey().isValid() - ) { - return Optional.ofNullable(loader.apply(context.getBuildCacheKey())); - } else { - return Optional.empty(); - } - } + public Optional shouldDisableCaching() { + if (task.isHasCustomActions()) { + LOGGER.info("Custom actions are attached to {}.", task); + } - @Override - public void store(Consumer storer) { - if (buildCacheEnabled - && context.isTaskCachingEnabled() - && context.getBuildCacheKey().isValid() - ) { - storer.accept(context.getBuildCacheKey()); - } - } - }; + return taskCacheabilityResolver.shouldDisableCaching( + context.getTaskProperties().hasDeclaredOutputs(), + context.getTaskProperties().getOutputFileProperties(), + task, + task.getOutputs().getCacheIfSpecs(), + task.getOutputs().getDoNotCacheIfSpecs(), + context.getOverlappingOutputs().orElse(null) + ); } @Override - public void outputsRemovedAfterFailureToLoadFromCache() { - context.setOutputRemovedBeforeExecution(true); + public boolean isAllowedToLoadFromCache() { + return context.getTaskExecutionMode().isAllowedToUseCachedResults(); } @Override @@ -245,6 +288,20 @@ public Optional getTimeout() { return Optional.ofNullable(task.getTimeout().getOrNull()); } + @Override + public void visitInputFileProperties(InputFilePropertyVisitor visitor) { + ImmutableSortedSet inputFileProperties = context.getTaskProperties().getInputFileProperties(); + for (InputFilePropertySpec inputFileProperty : inputFileProperties) { + Object value = inputFileProperty.getValue(); + boolean incremental = inputFileProperty.isIncremental() + // SkipWhenEmpty implies incremental. + // If this file property is empty, then we clean up the previously generated outputs. + // That means that there is a very close relation between the file property and the output. + || inputFileProperty.isSkipWhenEmpty(); + visitor.visitInputFileProperty(inputFileProperty.getPropertyName(), value, incremental); + } + } + @Override public ImmutableSortedMap snapshotAfterOutputsGenerated() { final AfterPreviousExecutionState afterPreviousExecutionState = context.getAfterPreviousExecution(); @@ -263,33 +320,23 @@ public ImmutableSortedMap apply(Overla } @Override - public void persistResult(final ImmutableSortedMap finalOutputs, final boolean successful, final OriginMetadata originMetadata) { - AfterPreviousExecutionState afterPreviousExecutionState = context.getAfterPreviousExecution(); - // Only persist history if there was no failure, or some output files have been changed - if (successful || afterPreviousExecutionState == null || hasAnyOutputFileChanges(afterPreviousExecutionState.getOutputFileProperties(), finalOutputs)) { - context.getBeforeExecutionState().ifPresent(new Consumer() { - @Override - public void accept(BeforeExecutionState execution) { - executionHistoryStore.store( - task.getPath(), - originMetadata, - execution.getImplementation(), - execution.getAdditionalImplementations(), - execution.getInputProperties(), - execution.getInputFileProperties(), - finalOutputs, - successful - ); - - outputFilesRepository.recordOutputs(finalOutputs.values()); - } - }); + public boolean isRequiresInputChanges() { + for (InputChangesAwareTaskAction taskAction : task.getTaskActions()) { + if (taskAction instanceof AbstractIncrementalTaskAction) { + return true; + } } + return false; } - private boolean hasAnyOutputFileChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { - return !previous.keySet().equals(current.keySet()) - || new OutputFileChanges(previous, current).hasAnyChanges(); + @Override + public boolean isRequiresLegacyInputChanges() { + for (InputChangesAwareTaskAction taskAction : task.getTaskActions()) { + if (taskAction instanceof IncrementalTaskInputsTaskAction) { + return true; + } + } + return false; } @Override @@ -297,18 +344,33 @@ public long markExecutionTime() { return context.markExecutionTime(); } + @Override + public void markSnapshottingInputsFinished(final CachingState cachingState) { + // TODO:lptr this should be added only if the scan plugin is applied, but SnapshotTaskInputsOperationIntegrationTest + // expects it to be added also when the build cache is enabled (but not the scan plugin) + if (buildCacheEnabled || scanPluginApplied) { + context.removeSnapshotTaskInputsBuildOperation() + .ifPresent(new Consumer() { + @Override + public void accept(ExecutingBuildOperation operation) { + operation.setResult(new SnapshotTaskInputsBuildOperationResult(cachingState)); + } + }); + } + } + @Override public String getDisplayName() { return task.toString(); } } - private void executeActions(TaskInternal task, TaskExecutionContext context) { - for (ContextAwareTaskAction action : new ArrayList(task.getTaskActions())) { + private void executeActions(TaskInternal task, @Nullable InputChangesInternal inputChanges) { + for (InputChangesAwareTaskAction action : new ArrayList(task.getTaskActions())) { task.getState().setDidWork(true); task.getStandardOutputCapture().start(); try { - executeAction(action.getDisplayName(), task, action, context); + executeAction(action.getDisplayName(), task, action, inputChanges); } catch (StopActionException e) { // Ignore LOGGER.debug("Action stopped by some action with message: {}", e.getMessage()); @@ -321,8 +383,10 @@ private void executeActions(TaskInternal task, TaskExecutionContext context) { } } - private void executeAction(final String actionDisplayName, final TaskInternal task, final ContextAwareTaskAction action, TaskExecutionContext context) { - action.contextualise(context); + private void executeAction(final String actionDisplayName, final TaskInternal task, final InputChangesAwareTaskAction action, @Nullable InputChangesInternal inputChanges) { + if (inputChanges != null) { + action.setInputChanges(inputChanges); + } buildOperationExecutor.run(new RunnableBuildOperation() { @Override public BuildOperationDescriptor.Builder description() { @@ -338,7 +402,7 @@ public void run(BuildOperationContext context) { } catch (Throwable t) { actionFailure = t; } finally { - action.releaseContext(); + action.clearInputChanges(); } try { diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationResult.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationResult.java index d33ce6eea9c3f..1d93754364b8b 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationResult.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationResult.java @@ -16,11 +16,13 @@ package org.gradle.api.internal.tasks.execution; -import org.gradle.api.internal.TaskOutputCachingState; -import org.gradle.api.internal.tasks.TaskExecutionContext; +import com.google.common.collect.ImmutableList; import org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory; import org.gradle.api.internal.tasks.TaskStateInternal; import org.gradle.caching.internal.origin.OriginMetadata; +import org.gradle.internal.execution.caching.CachingDisabledReason; +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory; +import org.gradle.internal.execution.caching.CachingState; import org.gradle.internal.id.UniqueId; import javax.annotation.Nullable; @@ -29,13 +31,17 @@ public class ExecuteTaskBuildOperationResult implements ExecuteTaskBuildOperationType.Result { private final TaskStateInternal taskState; - private final TaskExecutionContext ctx; + private final CachingState cachingState; private final OriginMetadata originMetadata; + private final boolean incremental; + private final List executionReasons; - public ExecuteTaskBuildOperationResult(TaskStateInternal taskState, TaskExecutionContext ctx, @Nullable OriginMetadata originMetadata) { + public ExecuteTaskBuildOperationResult(TaskStateInternal taskState, CachingState cachingState, @Nullable OriginMetadata originMetadata, boolean incremental, List executionReasons) { this.taskState = taskState; - this.ctx = ctx; + this.cachingState = cachingState; this.originMetadata = originMetadata; + this.incremental = incremental; + this.executionReasons = executionReasons; } @Nullable @@ -65,28 +71,58 @@ public Long getOriginExecutionTime() { @Nullable @Override public String getCachingDisabledReasonMessage() { - TaskOutputCachingState taskOutputCaching = taskState.getTaskOutputCaching(); - return taskOutputCaching.getDisabledReason(); + ImmutableList disabledReasons = cachingState.getDisabledReasons(); + return disabledReasons.isEmpty() + ? null + : disabledReasons.get(0).getMessage(); } @Nullable @Override public String getCachingDisabledReasonCategory() { - TaskOutputCachingState taskOutputCaching = taskState.getTaskOutputCaching(); - TaskOutputCachingDisabledReasonCategory disabledReasonCategory = taskOutputCaching.getDisabledReasonCategory(); - return disabledReasonCategory == null ? null : disabledReasonCategory.name(); + ImmutableList disabledReasons = cachingState.getDisabledReasons(); + return disabledReasons.isEmpty() + ? null + : convertNoCacheReasonCategory(disabledReasons.get(0).getCategory()).name(); + } + private static TaskOutputCachingDisabledReasonCategory convertNoCacheReasonCategory(CachingDisabledReasonCategory category) { + switch (category) { + case UNKNOWN: + return TaskOutputCachingDisabledReasonCategory.UNKNOWN; + case BUILD_CACHE_DISABLED: + return TaskOutputCachingDisabledReasonCategory.BUILD_CACHE_DISABLED; + case NOT_CACHEABLE: + return TaskOutputCachingDisabledReasonCategory.NOT_ENABLED_FOR_TASK; + case ENABLE_CONDITION_NOT_SATISFIED: + return TaskOutputCachingDisabledReasonCategory.CACHE_IF_SPEC_NOT_SATISFIED; + case DISABLE_CONDITION_SATISFIED: + return TaskOutputCachingDisabledReasonCategory.DO_NOT_CACHE_IF_SPEC_SATISFIED; + case NO_OUTPUTS_DECLARED: + return TaskOutputCachingDisabledReasonCategory.NO_OUTPUTS_DECLARED; + case NON_CACHEABLE_OUTPUT: + return TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TREE_OUTPUT; + case OVERLAPPING_OUTPUTS: + return TaskOutputCachingDisabledReasonCategory.OVERLAPPING_OUTPUTS; + case NON_CACHEABLE_IMPLEMENTATION: + return TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TASK_IMPLEMENTATION; + case NON_CACHEABLE_ADDITIONAL_IMPLEMENTATION: + return TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TASK_ACTION; + case NON_CACHEABLE_INPUTS: + return TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_INPUTS; + default: + throw new AssertionError(); + } } - @Nullable @Override public List getUpToDateMessages() { - return ctx.getUpToDateMessages(); + return executionReasons; } @Override public boolean isIncremental() { - return ctx.isTaskExecutedIncrementally(); + return incremental; } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationType.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationType.java index 5e39049e187a0..e3050f486e43b 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationType.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ExecuteTaskBuildOperationType.java @@ -16,7 +16,6 @@ package org.gradle.api.internal.tasks.execution; -import org.gradle.api.tasks.incremental.IncrementalTaskInputs; import org.gradle.internal.operations.BuildOperationType; import org.gradle.internal.scan.NotUsedByScanPlugin; import org.gradle.internal.scan.UsedByScanPlugin; @@ -124,7 +123,7 @@ public interface Result { /** * Returns if this task was executed incrementally. * - * @see IncrementalTaskInputs#isIncremental() + * @see org.gradle.work.InputChanges#isIncremental() */ @NotUsedByScanPlugin("used to report incrementality to TAPI progress listeners") boolean isIncremental(); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/FinishSnapshotTaskInputsBuildOperationTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/FinishSnapshotTaskInputsBuildOperationTaskExecuter.java deleted file mode 100644 index 86fdc1f3a15d3..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/FinishSnapshotTaskInputsBuildOperationTaskExecuter.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks.execution; - -import org.gradle.api.internal.TaskInternal; -import org.gradle.api.internal.tasks.TaskExecuter; -import org.gradle.api.internal.tasks.TaskExecuterResult; -import org.gradle.api.internal.tasks.TaskExecutionContext; -import org.gradle.api.internal.tasks.TaskStateInternal; -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey; -import org.gradle.internal.operations.ExecutingBuildOperation; - -import java.util.function.Consumer; - -/** - * {@inheritDoc} - */ -public class FinishSnapshotTaskInputsBuildOperationTaskExecuter implements SnapshotTaskInputsMeasuringTaskExecuter { - private final TaskExecuter delegate; - - public FinishSnapshotTaskInputsBuildOperationTaskExecuter( - TaskExecuter delegate - ) { - this.delegate = delegate; - } - - @Override - public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, final TaskExecutionContext context) { - context.removeSnapshotTaskInputsBuildOperation() - .ifPresent(new Consumer() { - @Override - public void accept(ExecutingBuildOperation operation) { - TaskOutputCachingBuildCacheKey cacheKey = context.getBuildCacheKey(); - operation.setResult(new OperationResultImpl(cacheKey)); - } - }); - return delegate.execute(task, state, context); - } -} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveBeforeExecutionStateTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveBeforeExecutionStateTaskExecuter.java index f2b495e56534e..7f9b34362e4f6 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveBeforeExecutionStateTaskExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveBeforeExecutionStateTaskExecuter.java @@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableSortedMap; import org.gradle.api.UncheckedIOException; import org.gradle.api.internal.TaskInternal; -import org.gradle.api.internal.tasks.ContextAwareTaskAction; +import org.gradle.api.internal.tasks.InputChangesAwareTaskAction; import org.gradle.api.internal.tasks.TaskExecuter; import org.gradle.api.internal.tasks.TaskExecuterResult; import org.gradle.api.internal.tasks.TaskExecutionContext; @@ -77,7 +77,7 @@ public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, Ta private BeforeExecutionState createExecutionState(TaskInternal task, TaskProperties properties, @Nullable AfterPreviousExecutionState afterPreviousExecutionState, ImmutableSortedMap outputFiles) { Class taskClass = task.getClass(); - List taskActions = task.getTaskActions(); + List taskActions = task.getTaskActions(); ImplementationSnapshot taskImplementation = ImplementationSnapshot.of(taskClass, classLoaderHierarchyHasher); ImmutableList taskActionImplementations = collectActionImplementations(taskActions, classLoaderHierarchyHasher); @@ -101,12 +101,12 @@ private BeforeExecutionState createExecutionState(TaskInternal task, TaskPropert ); } - private static ImmutableList collectActionImplementations(Collection taskActions, ClassLoaderHierarchyHasher classLoaderHierarchyHasher) { + private static ImmutableList collectActionImplementations(Collection taskActions, ClassLoaderHierarchyHasher classLoaderHierarchyHasher) { if (taskActions.isEmpty()) { return ImmutableList.of(); } ImmutableList.Builder actionImplementations = ImmutableList.builder(); - for (ContextAwareTaskAction taskAction : taskActions) { + for (InputChangesAwareTaskAction taskAction : taskActions) { actionImplementations.add(taskAction.getActionImplementation(classLoaderHierarchyHasher)); } return actionImplementations.build(); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveBuildCacheKeyExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveBuildCacheKeyExecuter.java deleted file mode 100644 index 3df7bd84a8db8..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveBuildCacheKeyExecuter.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks.execution; - -import org.gradle.api.internal.TaskInternal; -import org.gradle.api.internal.tasks.TaskExecuter; -import org.gradle.api.internal.tasks.TaskExecuterResult; -import org.gradle.api.internal.tasks.TaskExecutionContext; -import org.gradle.api.internal.tasks.TaskStateInternal; -import org.gradle.api.internal.tasks.properties.TaskProperties; -import org.gradle.api.logging.LogLevel; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; -import org.gradle.caching.internal.tasks.BuildCacheKeyInputs; -import org.gradle.caching.internal.tasks.TaskCacheKeyCalculator; -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey; -import org.gradle.internal.execution.history.BeforeExecutionState; -import org.gradle.util.Path; - -import java.util.function.Function; - -public class ResolveBuildCacheKeyExecuter implements TaskExecuter { - - private static final Logger LOGGER = Logging.getLogger(ResolveBuildCacheKeyExecuter.class); - private static final BuildCacheKeyInputs NO_CACHE_KEY_INPUTS = new BuildCacheKeyInputs( - null, - null, - null, - null, - null, - null - ); - - public static final TaskOutputCachingBuildCacheKey NO_CACHE_KEY = new TaskOutputCachingBuildCacheKey() { - @Override - public boolean isValid() { - return false; - } - - @Override - public String toString() { - return "INVALID"; - } - - @Override - public Path getTaskPath() { - throw new UnsupportedOperationException(); - } - - @Override - public String getHashCode() { - throw new UnsupportedOperationException(); - } - - @Override - public BuildCacheKeyInputs getInputs() { - return NO_CACHE_KEY_INPUTS; - } - - @Override - public byte[] getHashCodeBytes() { - return null; - } - - @Override - public String getDisplayName() { - return toString(); - } - }; - - private final TaskCacheKeyCalculator calculator; - private final boolean buildCacheDebugLogging; - private final TaskExecuter delegate; - - public ResolveBuildCacheKeyExecuter( - TaskCacheKeyCalculator calculator, - boolean buildCacheDebugLogging, - TaskExecuter delegate - ) { - this.calculator = calculator; - this.buildCacheDebugLogging = buildCacheDebugLogging; - this.delegate = delegate; - } - - @Override - public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) { - TaskOutputCachingBuildCacheKey cacheKey = resolve(task, context); - context.setBuildCacheKey(cacheKey); - return delegate.execute(task, state, context); - } - - private TaskOutputCachingBuildCacheKey resolve(final TaskInternal task, TaskExecutionContext context) { - final TaskProperties properties = context.getTaskProperties(); - return context.getBeforeExecutionState() - .map(new Function() { - @Override - public TaskOutputCachingBuildCacheKey apply(BeforeExecutionState beforeExecutionState) { - TaskOutputCachingBuildCacheKey cacheKey = calculator.calculate(task, beforeExecutionState, properties, buildCacheDebugLogging); - if (properties.hasDeclaredOutputs() && cacheKey.isValid()) { // A task with no outputs has no cache key. - LogLevel logLevel = buildCacheDebugLogging ? LogLevel.LIFECYCLE : LogLevel.INFO; - LOGGER.log(logLevel, "Build cache key for {} is {}", task, cacheKey.getHashCode()); - } - return cacheKey; - } - }) - .orElse(NO_CACHE_KEY); - } -} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveIncrementalChangesTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveIncrementalChangesTaskExecuter.java deleted file mode 100644 index e7da7329e03b8..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveIncrementalChangesTaskExecuter.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks.execution; - -import org.gradle.api.Describable; -import org.gradle.api.internal.TaskInternal; -import org.gradle.api.internal.tasks.TaskExecuter; -import org.gradle.api.internal.tasks.TaskExecuterResult; -import org.gradle.api.internal.tasks.TaskExecutionContext; -import org.gradle.api.internal.tasks.TaskStateInternal; -import org.gradle.internal.change.Change; -import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.DescriptiveChange; -import org.gradle.internal.execution.history.AfterPreviousExecutionState; -import org.gradle.internal.execution.history.BeforeExecutionState; -import org.gradle.internal.execution.history.changes.DefaultExecutionStateChanges; -import org.gradle.internal.execution.history.changes.ExecutionStateChanges; - -import javax.annotation.Nullable; -import java.util.function.Function; -import java.util.function.Supplier; - -/** - * Resolves the incremental changes to pass to the task actions. - * - * @see org.gradle.api.tasks.incremental.IncrementalTaskInputs - */ -public class ResolveIncrementalChangesTaskExecuter implements TaskExecuter { - private final TaskExecuter delegate; - - public ResolveIncrementalChangesTaskExecuter(TaskExecuter delegate) { - this.delegate = delegate; - } - - @Override - public TaskExecuterResult execute(final TaskInternal task, TaskStateInternal state, final TaskExecutionContext context) { - ExecutionStateChanges changes = context.getTaskExecutionMode().getRebuildReason() - .map(new Function() { - @Override - public ExecutionStateChanges apply(String rebuildReason) { - return new RebuildExecutionStateChanges(rebuildReason); - } - }).orElseGet(new Supplier() { - @Nullable - @Override - public ExecutionStateChanges get() { - final AfterPreviousExecutionState afterPreviousExecution = context.getAfterPreviousExecution(); - if (afterPreviousExecution == null || context.isOutputRemovedBeforeExecution()) { - return null; - } else { - // TODO We need a nicer describable wrapper around task here - return context.getBeforeExecutionState().map(new Function() { - @Override - public ExecutionStateChanges apply(BeforeExecutionState beforeExecution) { - return new DefaultExecutionStateChanges(afterPreviousExecution, beforeExecution, new Describable() { - @Override - public String getDisplayName() { - // The value is cached, so we should be okay to call this many times - return task.toString(); - } - }); - } - }).orElse(null); - } - } - }); - - context.setExecutionStateChanges(changes); - - return delegate.execute(task, state, context); - } - - private static class RebuildExecutionStateChanges implements ExecutionStateChanges { - private final Change rebuildChange; - - public RebuildExecutionStateChanges(String rebuildReason) { - this.rebuildChange = new DescriptiveChange(rebuildReason); - } - - @Override - public Iterable getInputFilesChanges() { - throw new UnsupportedOperationException(); - } - - @Override - public void visitAllChanges(ChangeVisitor visitor) { - visitor.visitChange(rebuildChange); - } - - @Override - public boolean isRebuildRequired() { - return true; - } - - @Override - public AfterPreviousExecutionState getPreviousExecution() { - throw new UnsupportedOperationException(); - } - } -} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveTaskOutputCachingStateExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveTaskOutputCachingStateExecuter.java deleted file mode 100644 index a66376fc6be53..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ResolveTaskOutputCachingStateExecuter.java +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks.execution; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSortedMap; -import com.google.common.collect.ImmutableSortedSet; -import org.gradle.api.internal.OverlappingOutputs; -import org.gradle.api.internal.TaskInternal; -import org.gradle.api.internal.TaskOutputCachingState; -import org.gradle.api.internal.tasks.DefaultTaskOutputCachingState; -import org.gradle.api.internal.tasks.TaskExecuter; -import org.gradle.api.internal.tasks.TaskExecuterResult; -import org.gradle.api.internal.tasks.TaskExecutionContext; -import org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory; -import org.gradle.api.internal.tasks.TaskStateInternal; -import org.gradle.api.internal.tasks.properties.CacheableOutputFilePropertySpec; -import org.gradle.api.internal.tasks.properties.OutputFilePropertySpec; -import org.gradle.caching.internal.tasks.BuildCacheKeyInputs; -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey; -import org.gradle.internal.file.RelativeFilePathResolver; -import org.gradle.internal.snapshot.impl.ImplementationSnapshot; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.BUILD_CACHE_DISABLED; -import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.CACHE_IF_SPEC_NOT_SATISFIED; -import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.DO_NOT_CACHE_IF_SPEC_SATISFIED; -import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_INPUTS; -import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TASK_ACTION; -import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TASK_IMPLEMENTATION; -import static org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TREE_OUTPUT; - -public class ResolveTaskOutputCachingStateExecuter implements TaskExecuter { - private static final Logger LOGGER = LoggerFactory.getLogger(ResolveTaskOutputCachingStateExecuter.class); - - private static final TaskOutputCachingState ENABLED = DefaultTaskOutputCachingState.enabled(); - private static final TaskOutputCachingState DISABLED = DefaultTaskOutputCachingState.disabled(BUILD_CACHE_DISABLED, "Task output caching is disabled"); - private static final TaskOutputCachingState CACHING_NOT_ENABLED = DefaultTaskOutputCachingState.disabled(TaskOutputCachingDisabledReasonCategory.NOT_ENABLED_FOR_TASK, "Caching has not been enabled for the task"); - private static final TaskOutputCachingState NO_OUTPUTS_DECLARED = DefaultTaskOutputCachingState.disabled(TaskOutputCachingDisabledReasonCategory.NO_OUTPUTS_DECLARED, "No outputs declared"); - - private final boolean buildCacheEnabled; - private final RelativeFilePathResolver relativeFilePathResolver; - private final TaskExecuter delegate; - - public ResolveTaskOutputCachingStateExecuter(boolean buildCacheEnabled, RelativeFilePathResolver relativeFilePathResolver, TaskExecuter delegate) { - this.buildCacheEnabled = buildCacheEnabled; - this.relativeFilePathResolver = relativeFilePathResolver; - this.delegate = delegate; - } - - @Override - public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, TaskExecutionContext context) { - if (buildCacheEnabled) { - TaskOutputCachingState taskOutputCachingState = resolveCachingState( - context.getTaskProperties().hasDeclaredOutputs(), - context.getTaskProperties().getOutputFileProperties(), - context.getBuildCacheKey(), - task, - task.getOutputs().getCacheIfSpecs(), - task.getOutputs().getDoNotCacheIfSpecs(), - context.getOverlappingOutputs().orElse(null), - relativeFilePathResolver); - context.setTaskCachingEnabled(taskOutputCachingState.isEnabled()); - state.setTaskOutputCaching(taskOutputCachingState); - if (!taskOutputCachingState.isEnabled()) { - LOGGER.info("Caching disabled for {}: {}", task, taskOutputCachingState.getDisabledReason()); - } - } else { - state.setTaskOutputCaching(DISABLED); - } - return delegate.execute(task, state, context); - } - - @VisibleForTesting - static TaskOutputCachingState resolveCachingState( - boolean hasDeclaredOutputs, - ImmutableSortedSet outputFileProperties, - TaskOutputCachingBuildCacheKey buildCacheKey, - TaskInternal task, - Collection> cacheIfSpecs, - Collection> doNotCacheIfSpecs, - @Nullable OverlappingOutputs overlappingOutputs, - RelativeFilePathResolver relativeFilePathResolver) { - if (cacheIfSpecs.isEmpty()) { - return CACHING_NOT_ENABLED; - } - - if (!hasDeclaredOutputs) { - return NO_OUTPUTS_DECLARED; - } - - if (overlappingOutputs != null) { - String relativePath = relativeFilePathResolver.resolveAsRelativePath(overlappingOutputs.getOverlappedFilePath()); - return DefaultTaskOutputCachingState.disabled(TaskOutputCachingDisabledReasonCategory.OVERLAPPING_OUTPUTS, - String.format("Gradle does not know how file '%s' was created (output property '%s'). Task output caching requires exclusive access to output paths to guarantee correctness.", - relativePath, overlappingOutputs.getPropertyName())); - } - - for (OutputFilePropertySpec spec : outputFileProperties) { - if (!(spec instanceof CacheableOutputFilePropertySpec)) { - return DefaultTaskOutputCachingState.disabled( - NON_CACHEABLE_TREE_OUTPUT, - "Output property '" - + spec.getPropertyName() - + "' contains a file tree" - ); - } - } - - for (SelfDescribingSpec cacheIfSpec : cacheIfSpecs) { - if (!cacheIfSpec.isSatisfiedBy(task)) { - return DefaultTaskOutputCachingState.disabled( - CACHE_IF_SPEC_NOT_SATISFIED, - "'" + cacheIfSpec.getDisplayName() + "' not satisfied" - ); - } - } - - for (SelfDescribingSpec doNotCacheIfSpec : doNotCacheIfSpecs) { - if (doNotCacheIfSpec.isSatisfiedBy(task)) { - return DefaultTaskOutputCachingState.disabled( - DO_NOT_CACHE_IF_SPEC_SATISFIED, - "'" + doNotCacheIfSpec.getDisplayName() + "' satisfied" - ); - } - } - - if (!buildCacheKey.isValid()) { - return getCachingStateForInvalidCacheKey(buildCacheKey); - } - return ENABLED; - } - - private static TaskOutputCachingState getCachingStateForInvalidCacheKey(TaskOutputCachingBuildCacheKey buildCacheKey) { - BuildCacheKeyInputs buildCacheKeyInputs = buildCacheKey.getInputs(); - ImplementationSnapshot taskImplementation = buildCacheKeyInputs.getTaskImplementation(); - if (taskImplementation != null && taskImplementation.isUnknown()) { - return DefaultTaskOutputCachingState.disabled(NON_CACHEABLE_TASK_IMPLEMENTATION, "Task class " + taskImplementation.getUnknownReason()); - } - - List actionImplementations = buildCacheKeyInputs.getActionImplementations(); - if (actionImplementations != null && !actionImplementations.isEmpty()) { - for (ImplementationSnapshot actionImplementation : actionImplementations) { - if (actionImplementation.isUnknown()) { - return DefaultTaskOutputCachingState.disabled(NON_CACHEABLE_TASK_ACTION, "Task action " + actionImplementation.getUnknownReason()); - } - } - } - - ImmutableSortedMap invalidInputProperties = buildCacheKeyInputs.getNonCacheableInputProperties(); - if (invalidInputProperties != null && !invalidInputProperties.isEmpty()) { - StringBuilder builder = new StringBuilder(); - builder.append("Non-cacheable inputs: "); - boolean first = true; - for (Map.Entry entry : Preconditions.checkNotNull(invalidInputProperties).entrySet()) { - if (!first) { - builder.append(", "); - } - first = false; - builder - .append("property '") - .append(entry.getKey()) - .append("' ") - .append(entry.getValue()); - } - return DefaultTaskOutputCachingState.disabled( - NON_CACHEABLE_INPUTS, - builder.toString() - ); - } - throw new IllegalStateException("Cache key is invalid without a known reason: " + buildCacheKey); - } -} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuter.java index 33112d126c6c8..162f8f7606e84 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuter.java @@ -26,8 +26,6 @@ import org.gradle.api.internal.tasks.TaskExecutionOutcome; import org.gradle.api.internal.tasks.TaskStateInternal; import org.gradle.api.internal.tasks.properties.TaskProperties; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.Cast; import org.gradle.internal.cleanup.BuildOutputCleanupRegistry; import org.gradle.internal.execution.OutputChangeListener; @@ -35,6 +33,8 @@ import org.gradle.internal.execution.history.ExecutionHistoryStore; import org.gradle.internal.fingerprint.FileCollectionFingerprint; import org.gradle.util.GFileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; @@ -42,7 +42,7 @@ * A {@link TaskExecuter} which skips tasks whose source file collection is empty. */ public class SkipEmptySourceFilesTaskExecuter implements TaskExecuter { - private static final Logger LOGGER = Logging.getLogger(SkipEmptySourceFilesTaskExecuter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SkipEmptySourceFilesTaskExecuter.class); private final TaskInputsListener taskInputsListener; private final BuildOutputCleanupRegistry buildOutputCleanupRegistry; private final OutputChangeListener outputChangeListener; @@ -63,7 +63,6 @@ public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, fi FileCollection sourceFiles = properties.getSourceFiles(); if (properties.hasSourceFiles() && sourceFiles.isEmpty()) { AfterPreviousExecutionState previousExecution = context.getAfterPreviousExecution(); - @SuppressWarnings("RedundantTypeArguments") ImmutableSortedMap outputFiles = previousExecution == null ? ImmutableSortedMap.of() : previousExecution.getOutputFileProperties(); @@ -103,7 +102,7 @@ public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, fi } taskInputsListener.onExecute(task, Cast.cast(FileCollectionInternal.class, sourceFiles)); executionHistoryStore.remove(task.getPath()); - return TaskExecuterResult.NO_REUSED_OUTPUT; + return TaskExecuterResult.WITHOUT_OUTPUTS; } else { taskInputsListener.onExecute(task, Cast.cast(FileCollectionInternal.class, properties.getInputFiles())); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuter.java index 9023aa74fb760..e5430b4afe9d4 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuter.java @@ -23,14 +23,14 @@ import org.gradle.api.internal.tasks.TaskExecutionContext; import org.gradle.api.internal.tasks.TaskExecutionOutcome; import org.gradle.api.internal.tasks.TaskStateInternal; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A {@link org.gradle.api.internal.tasks.TaskExecuter} which skips tasks whose onlyIf predicate evaluates to false */ public class SkipOnlyIfTaskExecuter implements TaskExecuter { - private static final Logger LOGGER = Logging.getLogger(SkipOnlyIfTaskExecuter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SkipOnlyIfTaskExecuter.class); private final TaskExecuter executer; public SkipOnlyIfTaskExecuter(TaskExecuter executer) { @@ -44,13 +44,13 @@ public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, Ta skip = !task.getOnlyIf().isSatisfiedBy(task); } catch (Throwable t) { state.setOutcome(new GradleException(String.format("Could not evaluate onlyIf predicate for %s.", task), t)); - return TaskExecuterResult.NO_REUSED_OUTPUT; + return TaskExecuterResult.WITHOUT_OUTPUTS; } if (skip) { LOGGER.info("Skipping {} as task onlyIf is false.", task); state.setOutcome(TaskExecutionOutcome.SKIPPED); - return TaskExecuterResult.NO_REUSED_OUTPUT; + return TaskExecuterResult.WITHOUT_OUTPUTS; } return executer.execute(task, state, context); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuter.java index 147e9f02e83d5..84f41687a5d29 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SkipTaskWithNoActionsExecuter.java @@ -23,14 +23,14 @@ import org.gradle.api.internal.tasks.TaskExecutionContext; import org.gradle.api.internal.tasks.TaskExecutionOutcome; import org.gradle.api.internal.tasks.TaskStateInternal; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * A {@link org.gradle.api.internal.tasks.TaskExecuter} which skips tasks that have no actions. */ public class SkipTaskWithNoActionsExecuter implements TaskExecuter { - private static final Logger LOGGER = Logging.getLogger(SkipTaskWithNoActionsExecuter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SkipTaskWithNoActionsExecuter.class); private final TaskExecutionGraph taskExecutionGraph; private final TaskExecuter executer; @@ -52,7 +52,7 @@ public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, Ta } state.setActionable(false); state.setOutcome(upToDate ? TaskExecutionOutcome.UP_TO_DATE : TaskExecutionOutcome.EXECUTED); - return TaskExecuterResult.NO_REUSED_OUTPUT; + return TaskExecuterResult.WITHOUT_OUTPUTS; } return executer.execute(task, state, context); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SnapshotTaskInputsMeasuringTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SnapshotTaskInputsMeasuringTaskExecuter.java deleted file mode 100644 index 04190cbb7a5ff..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/SnapshotTaskInputsMeasuringTaskExecuter.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks.execution; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.collect.ImmutableSortedMap; -import com.google.common.collect.ImmutableSortedSet; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.gradle.api.NonNullApi; -import org.gradle.api.internal.tasks.SnapshotTaskInputsBuildOperationType; -import org.gradle.api.internal.tasks.TaskExecuter; -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.fingerprint.FileSystemLocationFingerprint; -import org.gradle.internal.hash.HashCode; -import org.gradle.internal.operations.trace.CustomOperationTraceSerialization; -import org.gradle.internal.snapshot.DirectorySnapshot; -import org.gradle.internal.snapshot.FileSystemLocationSnapshot; -import org.gradle.internal.snapshot.FileSystemSnapshotVisitor; -import org.gradle.internal.snapshot.impl.ImplementationSnapshot; - -import javax.annotation.Nullable; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -/** - * The {@link OperationResultImpl} operation represents the work of analyzing the task's inputs - * plus the calculating the cache key. - * - *

- * These two operations should be captured separately, but for historical reasons we don't yet do that. - * To reproduce this composite operation why capture across executors by starting an operation - * in {@link StartSnapshotTaskInputsBuildOperationTaskExecuter} and finished in {@link FinishSnapshotTaskInputsBuildOperationTaskExecuter}. - *

- */ -public interface SnapshotTaskInputsMeasuringTaskExecuter extends TaskExecuter { - OperationDetailsImpl DETAILS_INSTANCE = new OperationDetailsImpl(); - - class OperationDetailsImpl implements SnapshotTaskInputsBuildOperationType.Details {} - - class OperationResultImpl implements SnapshotTaskInputsBuildOperationType.Result, CustomOperationTraceSerialization { - - @VisibleForTesting - final TaskOutputCachingBuildCacheKey key; - - OperationResultImpl(TaskOutputCachingBuildCacheKey key) { - this.key = key; - } - - @Override - public Map getInputValueHashesBytes() { - ImmutableSortedMap inputHashes = key.getInputs().getInputValueHashes(); - if (inputHashes == null || inputHashes.isEmpty()) { - return null; - } else { - return Maps.transformValues(inputHashes, new Function() { - @Override - public byte[] apply(HashCode input) { - return input.toByteArray(); - } - }); - } - } - - @NonNullApi - private static class State implements VisitState, FileSystemSnapshotVisitor { - - final InputFilePropertyVisitor visitor; - - Map fingerprints; - String propertyName; - HashCode propertyHash; - String propertyNormalizationStrategyIdentifier; - String name; - String path; - HashCode hash; - int depth; - - public State(InputFilePropertyVisitor visitor) { - this.visitor = visitor; - } - - @Override - public String getPropertyName() { - return propertyName; - } - - @Override - public byte[] getPropertyHashBytes() { - return propertyHash.toByteArray(); - } - - @Override - public String getPropertyNormalizationStrategyName() { - return propertyNormalizationStrategyIdentifier; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getPath() { - return path; - } - - @Override - public byte[] getHashBytes() { - return hash.toByteArray(); - } - - @Override - public boolean preVisitDirectory(DirectorySnapshot physicalSnapshot) { - this.path = physicalSnapshot.getAbsolutePath(); - this.name = physicalSnapshot.getName(); - this.hash = null; - - if (depth++ == 0) { - visitor.preRoot(this); - } - - visitor.preDirectory(this); - - return true; - } - - @Override - public void visit(FileSystemLocationSnapshot snapshot) { - this.path = snapshot.getAbsolutePath(); - this.name = snapshot.getName(); - - FileSystemLocationFingerprint fingerprint = fingerprints.get(path); - if (fingerprint == null) { - return; - } - - this.hash = fingerprint.getNormalizedContentHash(); - - boolean isRoot = depth == 0; - if (isRoot) { - visitor.preRoot(this); - } - - visitor.file(this); - - if (isRoot) { - visitor.postRoot(); - } - } - - @Override - public void postVisitDirectory(DirectorySnapshot directorySnapshot) { - visitor.postDirectory(); - if (--depth == 0) { - visitor.postRoot(); - } - } - } - - @Override - public void visitInputFileProperties(InputFilePropertyVisitor visitor) { - State state = new State(visitor); - ImmutableSortedMap inputFiles = key.getInputs().getInputFiles(); - if (inputFiles == null) { - return; - } - for (Map.Entry entry : inputFiles.entrySet()) { - CurrentFileCollectionFingerprint fingerprint = entry.getValue(); - - state.propertyName = entry.getKey(); - state.propertyHash = fingerprint.getHash(); - state.propertyNormalizationStrategyIdentifier = fingerprint.getStrategyIdentifier(); - state.fingerprints = fingerprint.getFingerprints(); - - visitor.preProperty(state); - fingerprint.accept(state); - visitor.postProperty(); - } - } - - @Nullable - @Override - public Set getInputPropertiesLoadedByUnknownClassLoader() { - SortedMap invalidInputProperties = key.getInputs().getNonCacheableInputProperties(); - if (invalidInputProperties == null || invalidInputProperties.isEmpty()) { - return null; - } - return invalidInputProperties.keySet(); - } - - - @Override - public byte[] getClassLoaderHashBytes() { - ImplementationSnapshot taskImplementation = key.getInputs().getTaskImplementation(); - if (taskImplementation == null || taskImplementation.getClassLoaderHash() == null) { - return null; - } - return taskImplementation.getClassLoaderHash().toByteArray(); - } - - @Override - public List getActionClassLoaderHashesBytes() { - List actionImplementations = key.getInputs().getActionImplementations(); - if (actionImplementations == null || actionImplementations.isEmpty()) { - return null; - } else { - return Lists.transform(actionImplementations, new Function() { - @Override - public byte[] apply(ImplementationSnapshot input) { - return input.getClassLoaderHash() == null ? null : input.getClassLoaderHash().toByteArray(); - } - }); - } - } - - @Nullable - @Override - public List getActionClassNames() { - List actionImplementations = key.getInputs().getActionImplementations(); - if (actionImplementations == null || actionImplementations.isEmpty()) { - return null; - } else { - return Lists.transform(actionImplementations, new Function() { - @Override - public String apply(ImplementationSnapshot input) { - return input.getTypeName(); - } - }); - } - } - - @Nullable - @Override - public List getOutputPropertyNames() { - // Copy should be a NOOP as this is an immutable sorted set upstream. - ImmutableSortedSet outputPropertyNames = key.getInputs().getOutputPropertyNames(); - if (outputPropertyNames == null || outputPropertyNames.isEmpty()) { - return null; - } else { - return ImmutableSortedSet.copyOf(outputPropertyNames).asList(); - } - } - - @Override - public byte[] getHashBytes() { - return key.isValid() ? key.getHashCodeBytes() : null; - } - - @Override - public Object getCustomOperationTraceSerializableModel() { - Map model = new TreeMap(); - - final Function bytesToString = new Function() { - @Nullable - @Override - public String apply(@Nullable byte[] input) { - if (input == null) { - return null; - } - return HashCode.fromBytes(input).toString(); - } - }; - - List actionClassLoaderHashesBytes = getActionClassLoaderHashesBytes(); - if (actionClassLoaderHashesBytes != null) { - model.put("actionClassLoaderHashes", Lists.transform(getActionClassLoaderHashesBytes(), bytesToString)); - } else { - model.put("actionClassLoaderHashes", null); - } - - model.put("actionClassNames", getActionClassNames()); - - byte[] hashBytes = getHashBytes(); - if (hashBytes != null) { - model.put("hash", HashCode.fromBytes(hashBytes).toString()); - } else { - model.put("hash", null); - } - - byte[] classLoaderHashBytes = getClassLoaderHashBytes(); - if (classLoaderHashBytes != null) { - model.put("classLoaderHash", HashCode.fromBytes(classLoaderHashBytes).toString()); - } else { - model.put("classLoaderHash", null); - } - - - model.put("inputFileProperties", fileProperties()); - - model.put("inputPropertiesLoadedByUnknownClassLoader", getInputPropertiesLoadedByUnknownClassLoader()); - - Map inputValueHashesBytes = getInputValueHashesBytes(); - if (inputValueHashesBytes != null) { - model.put("inputValueHashes", Maps.transformEntries(inputValueHashesBytes, new Maps.EntryTransformer() { - @Nullable - @Override - public String transformEntry(@Nullable String key, @Nullable byte[] value) { - if (value == null) { - return null; - } - return HashCode.fromBytes(value).toString(); - } - })); - } else { - model.put("inputValueHashes", null); - } - - model.put("outputPropertyNames", getOutputPropertyNames()); - - return model; - } - - protected Map fileProperties() { - final Map fileProperties = new TreeMap(); - visitInputFileProperties(new InputFilePropertyVisitor() { - Property property; - Deque dirStack = new ArrayDeque(); - - class Property { - private final String hash; - private final String normalization; - private final List roots = new ArrayList(); - - public Property(String hash, String normalization) { - this.hash = hash; - this.normalization = normalization; - } - - public String getHash() { - return hash; - } - - public String getNormalization() { - return normalization; - } - - public Collection getRoots() { - return roots; - } - } - - abstract class Entry { - private final String path; - - public Entry(String path) { - this.path = path; - } - - public String getPath() { - return path; - } - - } - - class FileEntry extends Entry { - private final String hash; - - FileEntry(String path, String hash) { - super(path); - this.hash = hash; - } - - public String getHash() { - return hash; - } - } - - class DirEntry extends Entry { - private final List children = new ArrayList(); - - DirEntry(String path) { - super(path); - } - - public Collection getChildren() { - return children; - } - } - - @Override - public void preProperty(VisitState state) { - property = new Property(HashCode.fromBytes(state.getPropertyHashBytes()).toString(), state.getPropertyNormalizationStrategyName()); - fileProperties.put(state.getPropertyName(), property); - } - - @Override - public void preRoot(VisitState state) { - - } - - @Override - public void preDirectory(VisitState state) { - boolean isRoot = dirStack.isEmpty(); - DirEntry dir = new DirEntry(isRoot ? state.getPath() : state.getName()); - if (isRoot) { - property.roots.add(dir); - } else { - //noinspection ConstantConditions - dirStack.peek().children.add(dir); - } - dirStack.push(dir); - } - - @Override - public void file(VisitState state) { - boolean isRoot = dirStack.isEmpty(); - FileEntry file = new FileEntry(isRoot ? state.getPath() : state.getName(), HashCode.fromBytes(state.getHashBytes()).toString()); - if (isRoot) { - property.roots.add(file); - } else { - //noinspection ConstantConditions - dirStack.peek().children.add(file); - } - } - - @Override - public void postDirectory() { - dirStack.pop(); - } - - @Override - public void postRoot() { - - } - - @Override - public void postProperty() { - - } - }); - return fileProperties; - } - - } - -} - diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/StartSnapshotTaskInputsBuildOperationTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/StartSnapshotTaskInputsBuildOperationTaskExecuter.java index 15a17771a7268..03aa888abd8e3 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/StartSnapshotTaskInputsBuildOperationTaskExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/StartSnapshotTaskInputsBuildOperationTaskExecuter.java @@ -17,17 +17,27 @@ package org.gradle.api.internal.tasks.execution; import org.gradle.api.internal.TaskInternal; +import org.gradle.api.internal.tasks.SnapshotTaskInputsBuildOperationResult; +import org.gradle.api.internal.tasks.SnapshotTaskInputsBuildOperationType; import org.gradle.api.internal.tasks.TaskExecuter; import org.gradle.api.internal.tasks.TaskExecuterResult; import org.gradle.api.internal.tasks.TaskExecutionContext; import org.gradle.api.internal.tasks.TaskStateInternal; +import org.gradle.internal.execution.caching.CachingState; import org.gradle.internal.operations.BuildOperationDescriptor; import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.ExecutingBuildOperation; import java.util.function.Consumer; -public class StartSnapshotTaskInputsBuildOperationTaskExecuter implements SnapshotTaskInputsMeasuringTaskExecuter { +/** + * The operation started here is finished in {@link MarkSnapshottingInputsFinishedStep}. + * + * @see SnapshotTaskInputsBuildOperationResult + */ +public class StartSnapshotTaskInputsBuildOperationTaskExecuter implements TaskExecuter { + private static final SnapshotTaskInputsBuildOperationType.Details DETAILS_INSTANCE = new SnapshotTaskInputsBuildOperationType.Details() {}; + private static final String BUILD_OPERATION_NAME = "Snapshot task inputs"; private final BuildOperationExecutor buildOperationExecutor; @@ -55,7 +65,7 @@ public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, Ta context.removeSnapshotTaskInputsBuildOperation().ifPresent(new Consumer() { @Override public void accept(ExecutingBuildOperation operation) { - operation.setResult(new OperationResultImpl(ResolveBuildCacheKeyExecuter.NO_CACHE_KEY)); + operation.setResult(new SnapshotTaskInputsBuildOperationResult(CachingState.NOT_DETERMINED)); } }); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/TaskCacheabilityResolver.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/TaskCacheabilityResolver.java new file mode 100644 index 0000000000000..05711fb07ea9e --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/TaskCacheabilityResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.execution; + +import com.google.common.collect.ImmutableSortedSet; +import org.gradle.api.internal.OverlappingOutputs; +import org.gradle.api.internal.TaskInternal; +import org.gradle.api.internal.tasks.properties.OutputFilePropertySpec; +import org.gradle.internal.execution.caching.CachingDisabledReason; + +import javax.annotation.Nullable; +import java.util.Collection; +import java.util.Optional; + +public interface TaskCacheabilityResolver { + Optional shouldDisableCaching( + boolean hasDeclaredOutputs, + ImmutableSortedSet outputFileProperties, + TaskInternal task, + Collection> cacheIfSpecs, + Collection> doNotCacheIfSpecs, + @Nullable OverlappingOutputs overlappingOutputs + ); +} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuter.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuter.java index 30784fa2efe67..48d4017bdfd49 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuter.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuter.java @@ -52,7 +52,7 @@ public TaskExecuterResult execute(TaskInternal task, TaskStateInternal state, Ta if (!messages.isEmpty()) { List firstMessages = messages.subList(0, Math.min(5, messages.size())); report(task, firstMessages, state); - return TaskExecuterResult.NO_REUSED_OUTPUT; + return TaskExecuterResult.WITHOUT_OUTPUTS; } return executer.execute(task, state, context); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/AbstractValidatingProperty.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/AbstractValidatingProperty.java index 0b6646eb3a204..91fb8545d535a 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/AbstractValidatingProperty.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/AbstractValidatingProperty.java @@ -37,7 +37,7 @@ public void validate(TaskValidationContext context) { Object unpacked = DeferredUtil.unpack(value.call()); if (unpacked == null) { if (!optional) { - context.recordValidationMessage(String.format("No value has been specified for property '%s'.", propertyName)); + context.visitError(String.format("No value has been specified for property '%s'.", propertyName)); } } else { validationAction.validate(propertyName, unpacked, context); diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/CompositePropertyVisitor.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/CompositePropertyVisitor.java index 1f3d1d4d1ad55..fd5a710faaef0 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/CompositePropertyVisitor.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/CompositePropertyVisitor.java @@ -38,9 +38,9 @@ public boolean visitOutputFilePropertiesOnly() { } @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { for (PropertyVisitor visitor : visitors) { - visitor.visitInputFileProperty(propertyName, optional, skipWhenEmpty, fileNormalizer, value, filePropertyType); + visitor.visitInputFileProperty(propertyName, optional, skipWhenEmpty, incremental, fileNormalizer, value, filePropertyType); } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultInputFilePropertySpec.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultInputFilePropertySpec.java index 897b58ad3b1a2..8b55a28aec2b1 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultInputFilePropertySpec.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultInputFilePropertySpec.java @@ -19,16 +19,32 @@ import org.gradle.api.file.FileCollection; import org.gradle.api.tasks.FileNormalizer; +import javax.annotation.Nullable; + public class DefaultInputFilePropertySpec extends AbstractFilePropertySpec implements InputFilePropertySpec { private final boolean skipWhenEmpty; + private final boolean incremental; + private final PropertyValue value; - public DefaultInputFilePropertySpec(String propertyName, Class normalizer, FileCollection files, boolean skipWhenEmpty) { + public DefaultInputFilePropertySpec(String propertyName, Class normalizer, FileCollection files, PropertyValue value, boolean skipWhenEmpty, boolean incremental) { super(propertyName, normalizer, files); this.skipWhenEmpty = skipWhenEmpty; + this.incremental = incremental; + this.value = value; } @Override public boolean isSkipWhenEmpty() { return skipWhenEmpty; } + + public boolean isIncremental() { + return incremental; + } + + @Override + @Nullable + public Object getValue() { + return value.call(); + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultParameterValidationContext.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultParameterValidationContext.java index cde72375976c0..7e4fba423bd16 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultParameterValidationContext.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultParameterValidationContext.java @@ -29,16 +29,21 @@ public DefaultParameterValidationContext(Collection messages) { } @Override - public void recordValidationMessage(@Nullable String ownerPath, String propertyName, String message) { + public void visitError(@Nullable String ownerPath, String propertyName, String message) { if (ownerPath == null) { - recordValidationMessage("Property '" + propertyName + "' " + message + "."); + visitError("Property '" + propertyName + "' " + message + "."); } else { - recordValidationMessage("Property '" + ownerPath + '.' + propertyName + "' " + message + "."); + visitError("Property '" + ownerPath + '.' + propertyName + "' " + message + "."); } } @Override - public void recordValidationMessage(String message) { + public void visitError(String message) { messages.add(message); } + + @Override + public void visitErrorStrict(String message) { + visitError(message); + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultTaskProperties.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultTaskProperties.java index 9b93f1985b212..169cdc603fea9 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultTaskProperties.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultTaskProperties.java @@ -249,7 +249,7 @@ private static class ValidationVisitor extends PropertyVisitor.Adapter { private final List taskPropertySpecs = new ArrayList(); @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { taskPropertySpecs.add(new DefaultFinalizingValidatingProperty(propertyName, value, optional, filePropertyType.getValidationAction())); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultTypeMetadataStore.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultTypeMetadataStore.java index 97cc01d3beed0..6bee320a7e368 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultTypeMetadataStore.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/DefaultTypeMetadataStore.java @@ -30,23 +30,25 @@ import org.gradle.api.internal.AbstractTask; import org.gradle.api.internal.ConventionTask; import org.gradle.api.internal.DynamicObjectAware; +import org.gradle.api.internal.GeneratedSubclasses; import org.gradle.api.internal.HasConvention; import org.gradle.api.internal.IConventionAware; import org.gradle.api.internal.tasks.properties.annotations.AbstractOutputPropertyAnnotationHandler; import org.gradle.api.internal.tasks.properties.annotations.OverridingPropertyAnnotationHandler; import org.gradle.api.internal.tasks.properties.annotations.PropertyAnnotationHandler; +import org.gradle.api.internal.tasks.properties.annotations.TypeAnnotationHandler; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.cache.internal.CrossBuildInMemoryCache; import org.gradle.cache.internal.CrossBuildInMemoryCacheFactory; -import org.gradle.internal.Pair; import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyExtractor; import org.gradle.internal.reflect.PropertyMetadata; import org.gradle.internal.reflect.ValidationProblem; import org.gradle.internal.scripts.ScriptOrigin; +import org.gradle.work.Incremental; import javax.annotation.Nullable; import java.lang.annotation.Annotation; @@ -57,12 +59,13 @@ public class DefaultTypeMetadataStore implements TypeMetadataStore { // Avoid reflecting on classes we know we don't need to look at private static final ImmutableSet> IGNORED_SUPER_CLASSES = ImmutableSet.of( - ConventionTask.class, DefaultTask.class, AbstractTask.class, Task.class, Object.class, GroovyObject.class, IConventionAware.class, ExtensionAware.class, HasConvention.class, ScriptOrigin.class, DynamicObjectAware.class + ConventionTask.class, DefaultTask.class, AbstractTask.class, Task.class, Object.class, GroovyObject.class, IConventionAware.class, ExtensionAware.class, HasConvention.class, ScriptOrigin.class, DynamicObjectAware.class ); private static final ImmutableSet> IGNORED_METHODS = ImmutableSet.of(Object.class, GroovyObject.class, ScriptOrigin.class); private final ImmutableMap, ? extends PropertyAnnotationHandler> annotationHandlers; + private final Collection typeAnnotationHandlers; private final CrossBuildInMemoryCache, TypeMetadata> cache; private final PropertyExtractor propertyExtractor; private Transformer> typeMetadataFactory = new Transformer>() { @@ -72,13 +75,14 @@ public TypeMetadata transform(Class type) { } }; - public DefaultTypeMetadataStore(Collection annotationHandlers, Set> otherKnownAnnotations, CrossBuildInMemoryCacheFactory cacheFactory) { + public DefaultTypeMetadataStore(Collection annotationHandlers, Set> otherKnownAnnotations, Collection typeAnnotationHandlers, CrossBuildInMemoryCacheFactory cacheFactory) { this.annotationHandlers = Maps.uniqueIndex(annotationHandlers, new Function>() { @Override public Class apply(PropertyAnnotationHandler handler) { return handler.getAnnotationType(); } }); + this.typeAnnotationHandlers = typeAnnotationHandlers; Multimap, Class> annotationOverrides = collectAnnotationOverrides(annotationHandlers); Set> relevantAnnotationTypes = collectRelevantAnnotationTypes(((Map, PropertyAnnotationHandler>) Maps.uniqueIndex(annotationHandlers, new Function>() { @Override @@ -114,11 +118,12 @@ private static Multimap, Class private static Set> collectRelevantAnnotationTypes(Set> propertyTypeAnnotations) { return ImmutableSet.>builder() - .addAll(propertyTypeAnnotations) - .add(Optional.class) - .add(SkipWhenEmpty.class) - .add(PathSensitive.class) - .build(); + .addAll(propertyTypeAnnotations) + .add(Optional.class) + .add(SkipWhenEmpty.class) + .add(PathSensitive.class) + .add(Incremental.class) + .build(); } @Override @@ -127,8 +132,61 @@ public TypeMetadata getTypeMetadata(final Class type) { } private TypeMetadata createTypeMetadata(Class type) { - Pair, ImmutableList> properties = propertyExtractor.extractPropertyMetadata(type); - return new DefaultTypeMetadata(properties.left, properties.right, annotationHandlers); + Class publicType = GeneratedSubclasses.unpack(type); + RecordingValidationContext validationContext = new RecordingValidationContext(); + for (TypeAnnotationHandler annotationHandler : typeAnnotationHandlers) { + if (publicType.isAnnotationPresent(annotationHandler.getAnnotationType())) { + annotationHandler.validateTypeMetadata(publicType, validationContext); + } + } + ImmutableSet properties = propertyExtractor.extractPropertyMetadata(publicType, validationContext); + ImmutableSet.Builder effectiveProperties = ImmutableSet.builderWithExpectedSize(properties.size()); + for (PropertyMetadata property : properties) { + PropertyAnnotationHandler annotationHandler = annotationHandlers.get(property.getPropertyType()); + annotationHandler.validatePropertyMetadata(property, validationContext); + if (annotationHandler.isPropertyRelevant()) { + effectiveProperties.add(property); + } + } + return new DefaultTypeMetadata(effectiveProperties.build(), validationContext.getProblems(), annotationHandlers); + } + + private static class RecordingValidationContext implements ParameterValidationContext { + private ImmutableList.Builder builder = ImmutableList.builder(); + + ImmutableList getProblems() { + return builder.build(); + } + + @Override + public void visitError(@Nullable String ownerPath, final String propertyName, final String message) { + builder.add(new ValidationProblem() { + @Override + public void collect(@Nullable String ownerPropertyPath, ParameterValidationContext validationContext) { + validationContext.visitError(ownerPropertyPath, propertyName, message); + } + }); + } + + @Override + public void visitError(final String message) { + builder.add(new ValidationProblem() { + @Override + public void collect(@Nullable String ownerPropertyPath, ParameterValidationContext validationContext) { + validationContext.visitError(message); + } + }); + } + + @Override + public void visitErrorStrict(final String message) { + builder.add(new ValidationProblem() { + @Override + public void collect(@Nullable String ownerPropertyPath, ParameterValidationContext validationContext) { + validationContext.visitErrorStrict(message); + } + }); + } } private static class DefaultTypeMetadata implements TypeMetadata { diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/GetInputFilesVisitor.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/GetInputFilesVisitor.java index 882e28fda2161..f72e7db768218 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/GetInputFilesVisitor.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/GetInputFilesVisitor.java @@ -40,13 +40,16 @@ public GetInputFilesVisitor(String ownerDisplayName, FileCollectionFactory fileC } @Override - public void visitInputFileProperty(final String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(final String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { FileCollection actualValue = FileParameterUtils.resolveInputFileValue(fileCollectionFactory, filePropertyType, value); specs.add(new DefaultInputFilePropertySpec( propertyName, FileParameterUtils.normalizerOrDefault(fileNormalizer), new PropertyFileCollection(ownerDisplayName, propertyName, "input", actualValue), - skipWhenEmpty)); + value, + skipWhenEmpty, + incremental + )); if (skipWhenEmpty) { hasSourceFiles = true; } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/InputFilePropertySpec.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/InputFilePropertySpec.java index 2f841959d1554..c1856614be7be 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/InputFilePropertySpec.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/InputFilePropertySpec.java @@ -16,6 +16,13 @@ package org.gradle.api.internal.tasks.properties; +import javax.annotation.Nullable; + public interface InputFilePropertySpec extends FilePropertySpec { boolean isSkipWhenEmpty(); + + boolean isIncremental(); + + @Nullable + Object getValue(); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/InspectionSchemeFactory.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/InspectionSchemeFactory.java index f826f41430794..c1efc66bcd2b9 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/InspectionSchemeFactory.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/InspectionSchemeFactory.java @@ -16,16 +16,14 @@ package org.gradle.api.internal.tasks.properties; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.UncheckedExecutionException; +import org.gradle.api.internal.tasks.properties.annotations.NoOpPropertyAnnotationHandler; import org.gradle.api.internal.tasks.properties.annotations.PropertyAnnotationHandler; +import org.gradle.api.internal.tasks.properties.annotations.TypeAnnotationHandler; import org.gradle.cache.internal.CrossBuildInMemoryCacheFactory; -import org.gradle.internal.UncheckedException; +import org.gradle.internal.instantiation.InstantiationScheme; import java.lang.annotation.Annotation; import java.util.Collection; @@ -34,57 +32,53 @@ import java.util.Set; public class InspectionSchemeFactory { - private final Map, PropertyAnnotationHandler> allKnownHandlers; + private final Map, PropertyAnnotationHandler> allKnownPropertyHandlers; + private final ImmutableList allKnownTypeHandlers; private final CrossBuildInMemoryCacheFactory cacheFactory; - // Assume for now that the annotations are all part of Gradle core and are never unloaded, so use strong references to the annotation types - private final LoadingCache>, InspectionScheme> schemes = CacheBuilder.newBuilder().build(new CacheLoader>, InspectionScheme>() { - @Override - public InspectionScheme load(Set> annotations) { - ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(annotations.size()); - for (Class annotation : annotations) { - PropertyAnnotationHandler handler = allKnownHandlers.get(annotation); - if (handler == null) { - throw new IllegalArgumentException(String.format("Annotation @%s is not a registered annotation.", annotation.getSimpleName())); - } - builder.add(handler); - } - ImmutableList annotationHandlers = builder.build(); - ImmutableSet.Builder> otherAnnotations = ImmutableSet.builderWithExpectedSize(allKnownHandlers.size() - annotations.size()); - for (Class annotation : allKnownHandlers.keySet()) { - if (!annotations.contains(annotation)) { - otherAnnotations.add(annotation); - } - } - return new InspectionSchemeImpl(annotationHandlers, otherAnnotations.build(), cacheFactory); - } - }); - public InspectionSchemeFactory(List allKnownHandlers, CrossBuildInMemoryCacheFactory cacheFactory) { + public InspectionSchemeFactory(List allKnownPropertyHandlers, List allKnownTypeHandlers, CrossBuildInMemoryCacheFactory cacheFactory) { ImmutableMap.Builder, PropertyAnnotationHandler> builder = ImmutableMap.builder(); - for (PropertyAnnotationHandler handler : allKnownHandlers) { + for (PropertyAnnotationHandler handler : allKnownPropertyHandlers) { builder.put(handler.getAnnotationType(), handler); } - this.allKnownHandlers = builder.build(); + this.allKnownPropertyHandlers = builder.build(); + this.allKnownTypeHandlers = ImmutableList.copyOf(allKnownTypeHandlers); this.cacheFactory = cacheFactory; } /** - * Creates an {@link InspectionScheme} with the given annotations enabled. + * Creates a new {@link InspectionScheme} with the given annotations enabled and using the given instantiation scheme. */ - public InspectionScheme inspectionScheme(Collection> annotations) { - try { - return schemes.getUnchecked(ImmutableSet.copyOf(annotations)); - } catch (UncheckedExecutionException e) { - throw UncheckedException.throwAsUncheckedException(e.getCause()); + public InspectionScheme inspectionScheme(Collection> annotations, InstantiationScheme instantiationScheme) { + ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(annotations.size()); + for (Class annotation : annotations) { + PropertyAnnotationHandler handler = allKnownPropertyHandlers.get(annotation); + if (handler == null) { + throw new IllegalArgumentException(String.format("Annotation @%s is not a registered annotation.", annotation.getSimpleName())); + } + builder.add(handler); + } + for (Class annotation : instantiationScheme.getInjectionAnnotations()) { + if (!annotations.contains(annotation)) { + builder.add(new NoOpPropertyAnnotationHandler(annotation)); + } + } + ImmutableList annotationHandlers = builder.build(); + ImmutableSet.Builder> otherAnnotations = ImmutableSet.builderWithExpectedSize(allKnownPropertyHandlers.size() - annotations.size()); + for (Class annotation : allKnownPropertyHandlers.keySet()) { + if (!annotations.contains(annotation)) { + otherAnnotations.add(annotation); + } } + return new InspectionSchemeImpl(annotationHandlers, otherAnnotations.build(), allKnownTypeHandlers, cacheFactory); } private static class InspectionSchemeImpl implements InspectionScheme { private final DefaultPropertyWalker propertyWalker; private final DefaultTypeMetadataStore metadataStore; - public InspectionSchemeImpl(List annotationHandlers, Set> otherKnownAnnotations, CrossBuildInMemoryCacheFactory cacheFactory) { - metadataStore = new DefaultTypeMetadataStore(annotationHandlers, otherKnownAnnotations, cacheFactory); + public InspectionSchemeImpl(List annotationHandlers, Set> otherKnownAnnotations, List typeHandlers, CrossBuildInMemoryCacheFactory cacheFactory) { + metadataStore = new DefaultTypeMetadataStore(annotationHandlers, otherKnownAnnotations, typeHandlers, cacheFactory); propertyWalker = new DefaultPropertyWalker(metadataStore); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyValidationAccess.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyValidationAccess.java index 298b2d48168fe..de694e271bbcf 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyValidationAccess.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyValidationAccess.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,11 @@ import org.gradle.api.Named; import org.gradle.api.NonNullApi; import org.gradle.api.Task; -import org.gradle.api.file.FileCollection; +import org.gradle.api.artifacts.transform.CacheableTransform; +import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.internal.TaskInternal; import org.gradle.api.internal.project.taskfactory.TaskClassInfoStore; import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.Input; import org.gradle.api.tasks.InputDirectory; import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFiles; @@ -44,7 +45,6 @@ import org.gradle.internal.service.scopes.PluginServiceRegistry; import javax.annotation.Nullable; -import java.io.File; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; @@ -54,20 +54,24 @@ import java.util.Queue; /** - * Class for easy access to task property validation from the validator task. + * Class for easy access to property validation from the validator task. */ @NonNullApi public class PropertyValidationAccess { - private final static Map, PropertyValidator> PROPERTY_VALIDATORS = ImmutableMap.of( - Input.class, new InputOnFileTypeValidator(), - InputFiles.class, new MissingPathSensitivityValidator(), - InputFile.class, new MissingPathSensitivityValidator(), - InputDirectory.class, new MissingPathSensitivityValidator() + private final static Map, ? extends PropertyValidator> PROPERTY_VALIDATORS = ImmutableMap.of( + InputFiles.class, new MissingPathSensitivityValidator(false), + InputFile.class, new MissingPathSensitivityValidator(false), + InputDirectory.class, new MissingPathSensitivityValidator(false) + ); + private final static Map, ? extends PropertyValidator> STRICT_PROPERTY_VALIDATORS = ImmutableMap.of( + InputFiles.class, new MissingPathSensitivityValidator(true), + InputFile.class, new MissingPathSensitivityValidator(true), + InputDirectory.class, new MissingPathSensitivityValidator(true) ); private static final PropertyValidationAccess INSTANCE = new PropertyValidationAccess(); private final TaskClassInfoStore taskClassInfoStore; - private final TypeMetadataStore metadataStore; + private final List typeSchemes; private PropertyValidationAccess() { ServiceRegistryBuilder builder = ServiceRegistryBuilder.builder().displayName("Global services"); @@ -86,24 +90,73 @@ void configure(ServiceRegistration registration) { }); ServiceRegistry services = builder.build(); taskClassInfoStore = services.get(TaskClassInfoStore.class); - metadataStore = services.get(TaskScheme.class).getInspectionScheme().getMetadataStore(); + typeSchemes = services.getAll(TypeScheme.class); } @SuppressWarnings("unused") public static void collectTaskValidationProblems(Class topLevelBean, Map problems, boolean enableStricterValidation) { - INSTANCE.collectValidationProblems(topLevelBean, problems, enableStricterValidation); + INSTANCE.collectTypeValidationProblems(topLevelBean, problems, enableStricterValidation); + } + + @SuppressWarnings("unused") + public static void collectValidationProblems(Class topLevelBean, Map problems, boolean enableStricterValidation) { + INSTANCE.collectTypeValidationProblems(topLevelBean, problems, enableStricterValidation); } - private void collectValidationProblems(Class topLevelBean, Map problems, boolean enableStricterValidation) { + private void collectTypeValidationProblems(Class topLevelBean, Map problems, boolean enableStricterValidation) { + // Skip this for now + if (topLevelBean.equals(TaskInternal.class)) { + return; + } + + TypeMetadataStore metadataStore = null; + for (TypeScheme typeScheme : typeSchemes) { + if (typeScheme.appliesTo(topLevelBean)) { + metadataStore = typeScheme.getMetadataStore(); + break; + } + } + if (metadataStore == null) { + // Don't know about this type + return; + } + + boolean cacheable; + boolean mapErrorsToWarnings; + if (Task.class.isAssignableFrom(topLevelBean)) { + cacheable = taskClassInfoStore.getTaskClassInfo(Cast.>uncheckedCast(topLevelBean)).isCacheable(); + // Treat all errors as warnings, for backwards compatibility + mapErrorsToWarnings = true; + } else if (TransformAction.class.isAssignableFrom(topLevelBean)) { + cacheable = topLevelBean.isAnnotationPresent(CacheableTransform.class); + mapErrorsToWarnings = false; + } else { + cacheable = false; + mapErrorsToWarnings = false; + } + Queue> queue = new ArrayDeque>(); BeanTypeNodeFactory nodeFactory = new BeanTypeNodeFactory(metadataStore); queue.add(nodeFactory.createRootNode(TypeToken.of(topLevelBean))); - boolean cacheable = taskClassInfoStore.getTaskClassInfo(Cast.>uncheckedCast(topLevelBean)).isCacheable(); boolean stricterValidation = enableStricterValidation || cacheable; while (!queue.isEmpty()) { BeanTypeNode node = queue.remove(); - node.visit(topLevelBean, stricterValidation, problems, queue, nodeFactory); + node.visit(topLevelBean, stricterValidation, new ProblemCollector(problems, mapErrorsToWarnings), queue, nodeFactory); + } + } + + private static class ProblemCollector { + final Map problems; + private final boolean mapErrorsToWarnings; + + public ProblemCollector(Map problems, boolean mapErrorsToWarnings) { + this.problems = problems; + this.mapErrorsToWarnings = mapErrorsToWarnings; + } + + void error(String message, boolean strict) { + problems.put(message, strict || !mapErrorsToWarnings); } } @@ -146,11 +199,12 @@ protected BeanTypeNode(@Nullable BeanTypeNode parentNode, @Nullable String pr this.beanType = beanType; } - public abstract void visit(Class topLevelBean, boolean stricterValidation, Map problems, Queue> queue, BeanTypeNodeFactory nodeFactory); + public abstract void visit(Class topLevelBean, boolean stricterValidation, ProblemCollector problems, Queue> queue, BeanTypeNodeFactory nodeFactory); public boolean nodeCreatesCycle(TypeToken childType) { return findNodeCreatingCycle(childType, Equivalence.equals()) != null; } + private final TypeToken beanType; protected TypeToken extractNestedType(Class parameterizedSuperClass, int typeParameterIndex) { @@ -170,19 +224,16 @@ public NestedBeanTypeNode(@Nullable BeanTypeNode parentNode, @Nullable String } @Override - public void visit(final Class topLevelBean, boolean stricterValidation, final Map problems, Queue> queue, BeanTypeNodeFactory nodeFactory) { + public void visit(final Class topLevelBean, boolean stricterValidation, ProblemCollector problems, Queue> queue, BeanTypeNodeFactory nodeFactory) { TypeMetadata typeMetadata = getTypeMetadata(); ParameterValidationContext validationContext = new CollectingParameterValidationContext(topLevelBean, problems); typeMetadata.collectValidationFailures(getPropertyName(), validationContext); for (PropertyMetadata propertyMetadata : typeMetadata.getPropertiesMetadata()) { String qualifiedPropertyName = getQualifiedPropertyName(propertyMetadata.getPropertyName()); Class propertyType = propertyMetadata.getPropertyType(); - PropertyValidator validator = PROPERTY_VALIDATORS.get(propertyType); + PropertyValidator validator = stricterValidation ? STRICT_PROPERTY_VALIDATORS.get(propertyType) : PROPERTY_VALIDATORS.get(propertyType); if (validator != null) { - String validationMessage = validator.validate(stricterValidation, propertyMetadata); - if (validationMessage != null) { - validationContext.recordValidationMessage(null, propertyMetadata.getPropertyName(), validationMessage); - } + validator.validate(null, propertyMetadata, validationContext); } if (propertyMetadata.isAnnotationPresent(Nested.class)) { TypeToken beanType = unpackProvider(propertyMetadata.getGetterMethod()); @@ -195,28 +246,39 @@ private static TypeToken unpackProvider(Method method) { Class rawType = method.getReturnType(); TypeToken genericReturnType = TypeToken.of(method.getGenericReturnType()); if (Provider.class.isAssignableFrom(rawType)) { - return PropertyValidationAccess.extractNestedType(Cast.>>uncheckedCast(genericReturnType), Provider.class, 0); + return PropertyValidationAccess.extractNestedType(Cast.>>uncheckedCast(genericReturnType), Provider.class, 0); } return genericReturnType; } private class CollectingParameterValidationContext implements ParameterValidationContext { private final Class topLevelBean; - private final Map problems; + private final ProblemCollector problems; - public CollectingParameterValidationContext(Class topLevelBean, Map problems) { + public CollectingParameterValidationContext(Class topLevelBean, ProblemCollector problems) { this.topLevelBean = topLevelBean; this.problems = problems; } @Override - public void recordValidationMessage(@Nullable String ownerPath, String propertyName, String message) { - recordValidationMessage(String.format("Task type '%s': property '%s' %s.", topLevelBean.getName(), getQualifiedPropertyName(propertyName), message)); + public void visitError(@Nullable String ownerPath, String propertyName, String message) { + String decoratedMessage; + if (Task.class.isAssignableFrom(topLevelBean)) { + decoratedMessage = String.format("Task type '%s': property '%s' %s.", topLevelBean.getName(), getQualifiedPropertyName(propertyName), message); + } else { + decoratedMessage = String.format("Type '%s': property '%s' %s.", topLevelBean.getName(), getQualifiedPropertyName(propertyName), message); + } + visitError(decoratedMessage); + } + + @Override + public void visitError(String message) { + problems.error(message, false); } @Override - public void recordValidationMessage(String message) { - problems.put(message, Boolean.FALSE); + public void visitErrorStrict(String message) { + problems.error(message, true); } } } @@ -234,7 +296,7 @@ private String determinePropertyName(TypeToken nestedType) { } @Override - public void visit(Class topLevelBean, boolean stricterValidation, Map problems, Queue> queue, BeanTypeNodeFactory nodeFactory) { + public void visit(Class topLevelBean, boolean stricterValidation, ProblemCollector problems, Queue> queue, BeanTypeNodeFactory nodeFactory) { TypeToken nestedType = extractNestedType(Iterable.class, 0); nodeFactory.createAndAddToQueue(this, determinePropertyName(nestedType), nestedType, queue); } @@ -247,41 +309,29 @@ public MapBeanTypeNode(@Nullable BeanTypeNode parentNode, @Nullable String pa } @Override - public void visit(Class topLevelBean, boolean stricterValidation, Map problems, Queue> queue, BeanTypeNodeFactory nodeFactory) { + public void visit(Class topLevelBean, boolean stricterValidation, ProblemCollector problems, Queue> queue, BeanTypeNodeFactory nodeFactory) { TypeToken nestedType = extractNestedType(Map.class, 1); nodeFactory.createAndAddToQueue(this, getQualifiedPropertyName(""), nestedType, queue); } } private interface PropertyValidator { - @Nullable - String validate(boolean stricterValidation, PropertyMetadata metadata); - } - - private static class InputOnFileTypeValidator implements PropertyValidator { - @Nullable - @Override - public String validate(boolean stricterValidation, PropertyMetadata metadata) { - Class valueType = metadata.getDeclaredType(); - if (File.class.isAssignableFrom(valueType) - || java.nio.file.Path.class.isAssignableFrom(valueType) - || FileCollection.class.isAssignableFrom(valueType)) { - return "has @Input annotation used on property of type " + valueType.getName(); - } - return null; - } + void validate(@Nullable String ownerPath, PropertyMetadata metadata, ParameterValidationContext validationContext); } private static class MissingPathSensitivityValidator implements PropertyValidator { + final boolean stricterValidation; + + public MissingPathSensitivityValidator(boolean stricterValidation) { + this.stricterValidation = stricterValidation; + } - @Nullable @Override - public String validate(boolean stricterValidation, PropertyMetadata metadata) { + public void validate(@Nullable String ownerPath, PropertyMetadata metadata, ParameterValidationContext validationContext) { PathSensitive pathSensitive = metadata.getAnnotation(PathSensitive.class); if (stricterValidation && pathSensitive == null) { - return "is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE"; + validationContext.visitError(ownerPath, metadata.getPropertyName(), "is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE"); } - return null; } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyVisitor.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyVisitor.java index bc68c861636be..5ee8a61df67f7 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyVisitor.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyVisitor.java @@ -35,7 +35,7 @@ public interface PropertyVisitor { */ boolean visitOutputFilePropertiesOnly(); - void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType); + void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType); void visitInputProperty(String propertyName, PropertyValue value, boolean optional); @@ -52,7 +52,7 @@ public boolean visitOutputFilePropertiesOnly() { } @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { } @Override diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/TaskScheme.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/TaskScheme.java index 24a9cf8c028b6..fb5f6b20343d7 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/TaskScheme.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/TaskScheme.java @@ -16,9 +16,10 @@ package org.gradle.api.internal.tasks.properties; +import org.gradle.api.Task; import org.gradle.internal.instantiation.InstantiationScheme; -public class TaskScheme { +public class TaskScheme implements TypeScheme { private final InstantiationScheme instantiationScheme; private final InspectionScheme inspectionScheme; @@ -27,6 +28,16 @@ public TaskScheme(InstantiationScheme instantiationScheme, InspectionScheme insp this.inspectionScheme = inspectionScheme; } + @Override + public TypeMetadataStore getMetadataStore() { + return inspectionScheme.getMetadataStore(); + } + + @Override + public boolean appliesTo(Class type) { + return Task.class.isAssignableFrom(type); + } + public InstantiationScheme getInstantiationScheme() { return instantiationScheme; } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/TypeScheme.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/TypeScheme.java new file mode 100644 index 0000000000000..ad65ffc636902 --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/TypeScheme.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.properties; + +/** + * Metadata about a particular set of types. + */ +public interface TypeScheme { + TypeMetadataStore getMetadataStore(); + + boolean appliesTo(Class type); +} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/ValidationActions.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/ValidationActions.java index 9b3fda9674fe7..fa0acd25ba033 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/ValidationActions.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/ValidationActions.java @@ -35,9 +35,9 @@ public void doValidate(String propertyName, Object value, TaskValidationContext public void doValidate(String propertyName, Object value, TaskValidationContext context) { File file = toFile(context, value); if (!file.exists()) { - context.recordValidationMessage(String.format("File '%s' specified for property '%s' does not exist.", file, propertyName)); + context.visitError(String.format("File '%s' specified for property '%s' does not exist.", file, propertyName)); } else if (!file.isFile()) { - context.recordValidationMessage(String.format("File '%s' specified for property '%s' is not a file.", file, propertyName)); + context.visitError(String.format("File '%s' specified for property '%s' is not a file.", file, propertyName)); } } }, @@ -46,9 +46,9 @@ public void doValidate(String propertyName, Object value, TaskValidationContext public void doValidate(String propertyName, Object value, TaskValidationContext context) { File directory = toDirectory(context, value); if (!directory.exists()) { - context.recordValidationMessage(String.format("Directory '%s' specified for property '%s' does not exist.", directory, propertyName)); + context.visitError(String.format("Directory '%s' specified for property '%s' does not exist.", directory, propertyName)); } else if (!directory.isDirectory()) { - context.recordValidationMessage(String.format("Directory '%s' specified for property '%s' is not a directory.", directory, propertyName)); + context.visitError(String.format("Directory '%s' specified for property '%s' is not a directory.", directory, propertyName)); } } }, @@ -58,12 +58,12 @@ public void doValidate(String propertyName, Object value, TaskValidationContext File directory = toFile(context, value); if (directory.exists()) { if (!directory.isDirectory()) { - context.recordValidationMessage(String.format("Directory '%s' specified for property '%s' is not a directory.", directory, propertyName)); + context.visitError(String.format("Directory '%s' specified for property '%s' is not a directory.", directory, propertyName)); } } else { for (File candidate = directory.getParentFile(); candidate != null && !candidate.isDirectory(); candidate = candidate.getParentFile()) { if (candidate.exists() && !candidate.isDirectory()) { - context.recordValidationMessage(String.format("Cannot write to directory '%s' specified for property '%s', as ancestor '%s' is not a directory.", directory, propertyName, candidate)); + context.visitError(String.format("Cannot write to directory '%s' specified for property '%s', as ancestor '%s' is not a directory.", directory, propertyName, candidate)); return; } } @@ -84,13 +84,13 @@ public void doValidate(String propertyName, Object value, TaskValidationContext File file = toFile(context, value); if (file.exists()) { if (file.isDirectory()) { - context.recordValidationMessage(String.format("Cannot write to file '%s' specified for property '%s' as it is a directory.", file, propertyName)); + context.visitError(String.format("Cannot write to file '%s' specified for property '%s' as it is a directory.", file, propertyName)); } // else, assume we can write to anything that exists and is not a directory } else { for (File candidate = file.getParentFile(); candidate != null && !candidate.isDirectory(); candidate = candidate.getParentFile()) { if (candidate.exists() && !candidate.isDirectory()) { - context.recordValidationMessage(String.format("Cannot write to file '%s' specified for property '%s', as ancestor '%s' is not a directory.", file, propertyName, candidate)); + context.visitError(String.format("Cannot write to file '%s' specified for property '%s', as ancestor '%s' is not a directory.", file, propertyName, candidate)); break; } } @@ -119,7 +119,7 @@ public void validate(String propertyName, Object value, TaskValidationContext co try { doValidate(propertyName, value, context); } catch (UnsupportedNotationException ignored) { - context.recordValidationMessage(String.format("Value '%s' specified for property '%s' cannot be converted to a %s.", value, propertyName, targetType)); + context.visitError(String.format("Value '%s' specified for property '%s' cannot be converted to a %s.", value, propertyName, targetType)); } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/AbstractInputFilePropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/AbstractInputFilePropertyAnnotationHandler.java index bd47367aac5dc..700ce398a86f3 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/AbstractInputFilePropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/AbstractInputFilePropertyAnnotationHandler.java @@ -25,9 +25,16 @@ import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; +import org.gradle.work.Incremental; public abstract class AbstractInputFilePropertyAnnotationHandler implements PropertyAnnotationHandler { + @Override + public boolean isPropertyRelevant() { + return true; + } + @Override public boolean shouldVisit(PropertyVisitor visitor) { return !visitor.visitOutputFilePropertiesOnly(); @@ -41,11 +48,16 @@ public void visitPropertyValue(String propertyName, PropertyValue value, Propert propertyName, propertyMetadata.isAnnotationPresent(Optional.class), propertyMetadata.isAnnotationPresent(SkipWhenEmpty.class), + propertyMetadata.isAnnotationPresent(Incremental.class), fileNormalizer, value, getFilePropertyType() ); } + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + } + protected abstract InputFilePropertyType getFilePropertyType(); } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/AbstractOutputPropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/AbstractOutputPropertyAnnotationHandler.java index 1bf04cf6e3690..7e0be99dddbba 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/AbstractOutputPropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/AbstractOutputPropertyAnnotationHandler.java @@ -21,12 +21,18 @@ import org.gradle.api.internal.tasks.properties.PropertyValue; import org.gradle.api.internal.tasks.properties.PropertyVisitor; import org.gradle.api.tasks.Optional; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; public abstract class AbstractOutputPropertyAnnotationHandler implements PropertyAnnotationHandler { protected abstract OutputFilePropertyType getFilePropertyType(); + @Override + public boolean isPropertyRelevant() { + return true; + } + @Override public boolean shouldVisit(PropertyVisitor visitor) { return true; @@ -36,4 +42,8 @@ public boolean shouldVisit(PropertyVisitor visitor) { public void visitPropertyValue(String propertyName, PropertyValue value, PropertyMetadata propertyMetadata, PropertyVisitor visitor, BeanPropertyContext context) { visitor.visitOutputFileProperty(propertyName, propertyMetadata.isAnnotationPresent(Optional.class), value, getFilePropertyType()); } + + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/CacheableTaskTypeAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/CacheableTaskTypeAnnotationHandler.java new file mode 100644 index 0000000000000..537369340c71c --- /dev/null +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/CacheableTaskTypeAnnotationHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.properties.annotations; + +import org.gradle.api.Task; +import org.gradle.api.tasks.CacheableTask; +import org.gradle.internal.reflect.ParameterValidationContext; +import org.gradle.model.internal.type.ModelType; + +import java.lang.annotation.Annotation; + +public class CacheableTaskTypeAnnotationHandler implements TypeAnnotationHandler { + @Override + public Class getAnnotationType() { + return CacheableTask.class; + } + + @Override + public void validateTypeMetadata(Class classWithAnnotationAttached, ParameterValidationContext visitor) { + if (!Task.class.isAssignableFrom(classWithAnnotationAttached)) { + visitor.visitErrorStrict(String.format("Cannot use @%s with type %s. This annotation can only be used with %s types.", getAnnotationType().getSimpleName(), ModelType.of(classWithAnnotationAttached).getDisplayName(), Task.class.getSimpleName())); + } + } +} diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/ClasspathPropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/ClasspathPropertyAnnotationHandler.java index 5a0f70df4d3d9..318d322564878 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/ClasspathPropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/ClasspathPropertyAnnotationHandler.java @@ -28,7 +28,9 @@ import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; +import org.gradle.work.Incremental; import java.lang.annotation.Annotation; @@ -38,6 +40,11 @@ public Class getAnnotationType() { return Classpath.class; } + @Override + public boolean isPropertyRelevant() { + return true; + } + @Override public ImmutableList> getOverriddenAnnotationTypes() { return ImmutableList.of(InputFiles.class, InputArtifact.class, InputArtifactDependencies.class); @@ -54,9 +61,14 @@ public void visitPropertyValue(String propertyName, PropertyValue value, Propert propertyName, propertyMetadata.isAnnotationPresent(Optional.class), propertyMetadata.isAnnotationPresent(SkipWhenEmpty.class), + propertyMetadata.isAnnotationPresent(Incremental.class), ClasspathNormalizer.class, value, InputFilePropertyType.FILES ); } + + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/CompileClasspathPropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/CompileClasspathPropertyAnnotationHandler.java index f2c12b2b8ed7b..869964a21b2d9 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/CompileClasspathPropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/CompileClasspathPropertyAnnotationHandler.java @@ -28,7 +28,9 @@ import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Optional; import org.gradle.api.tasks.SkipWhenEmpty; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; +import org.gradle.work.Incremental; import java.lang.annotation.Annotation; @@ -38,6 +40,11 @@ public Class getAnnotationType() { return CompileClasspath.class; } + @Override + public boolean isPropertyRelevant() { + return true; + } + @Override public ImmutableList> getOverriddenAnnotationTypes() { return ImmutableList.of(InputFiles.class, InputArtifact.class, InputArtifactDependencies.class); @@ -54,9 +61,14 @@ public void visitPropertyValue(String propertyName, PropertyValue value, Propert propertyName, propertyMetadata.isAnnotationPresent(Optional.class), propertyMetadata.isAnnotationPresent(SkipWhenEmpty.class), + propertyMetadata.isAnnotationPresent(Incremental.class), CompileClasspathNormalizer.class, value, InputFilePropertyType.FILES ); } + + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/DestroysPropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/DestroysPropertyAnnotationHandler.java index a37a3e5d59996..18e94ef794fc9 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/DestroysPropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/DestroysPropertyAnnotationHandler.java @@ -20,6 +20,7 @@ import org.gradle.api.internal.tasks.properties.PropertyValue; import org.gradle.api.internal.tasks.properties.PropertyVisitor; import org.gradle.api.tasks.Destroys; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; import java.lang.annotation.Annotation; @@ -30,6 +31,11 @@ public Class getAnnotationType() { return Destroys.class; } + @Override + public boolean isPropertyRelevant() { + return true; + } + @Override public boolean shouldVisit(PropertyVisitor visitor) { return !visitor.visitOutputFilePropertiesOnly(); @@ -39,4 +45,8 @@ public boolean shouldVisit(PropertyVisitor visitor) { public void visitPropertyValue(String propertyName, PropertyValue value, PropertyMetadata propertyMetadata, PropertyVisitor visitor, BeanPropertyContext context) { visitor.visitDestroyableProperty(value); } + + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/InputPropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/InputPropertyAnnotationHandler.java index 2c25b91efbb2a..dd90a6b38558f 100755 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/InputPropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/InputPropertyAnnotationHandler.java @@ -15,13 +15,16 @@ */ package org.gradle.api.internal.tasks.properties.annotations; +import org.gradle.api.file.FileCollection; import org.gradle.api.internal.tasks.properties.BeanPropertyContext; import org.gradle.api.internal.tasks.properties.PropertyValue; import org.gradle.api.internal.tasks.properties.PropertyVisitor; import org.gradle.api.tasks.Input; import org.gradle.api.tasks.Optional; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; +import java.io.File; import java.lang.annotation.Annotation; public class InputPropertyAnnotationHandler implements PropertyAnnotationHandler { @@ -29,6 +32,11 @@ public Class getAnnotationType() { return Input.class; } + @Override + public boolean isPropertyRelevant() { + return true; + } + @Override public boolean shouldVisit(PropertyVisitor visitor) { return !visitor.visitOutputFilePropertiesOnly(); @@ -38,4 +46,14 @@ public boolean shouldVisit(PropertyVisitor visitor) { public void visitPropertyValue(String propertyName, PropertyValue value, PropertyMetadata propertyMetadata, PropertyVisitor visitor, BeanPropertyContext context) { visitor.visitInputProperty(propertyName, value, propertyMetadata.isAnnotationPresent(Optional.class)); } + + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + Class valueType = propertyMetadata.getDeclaredType(); + if (File.class.isAssignableFrom(valueType) + || java.nio.file.Path.class.isAssignableFrom(valueType) + || FileCollection.class.isAssignableFrom(valueType)) { + visitor.visitError(null, propertyMetadata.getPropertyName(), "has @Input annotation used on property of type " + valueType.getName()); + } + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/LocalStatePropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/LocalStatePropertyAnnotationHandler.java index 7f41cb7875037..e129cba4c2ef8 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/LocalStatePropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/LocalStatePropertyAnnotationHandler.java @@ -19,6 +19,7 @@ import org.gradle.api.internal.tasks.properties.PropertyValue; import org.gradle.api.internal.tasks.properties.PropertyVisitor; import org.gradle.api.tasks.LocalState; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; import java.lang.annotation.Annotation; @@ -28,6 +29,11 @@ public Class getAnnotationType() { return LocalState.class; } + @Override + public boolean isPropertyRelevant() { + return true; + } + @Override public boolean shouldVisit(PropertyVisitor visitor) { return !visitor.visitOutputFilePropertiesOnly(); @@ -37,4 +43,8 @@ public boolean shouldVisit(PropertyVisitor visitor) { public void visitPropertyValue(String propertyName, PropertyValue value, PropertyMetadata propertyMetadata, PropertyVisitor visitor, BeanPropertyContext context) { visitor.visitLocalStateProperty(value); } + + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/NestedBeanAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/NestedBeanAnnotationHandler.java index 2636e7dc5c8c1..3be4f64f585d5 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/NestedBeanAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/NestedBeanAnnotationHandler.java @@ -24,6 +24,7 @@ import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.Optional; import org.gradle.internal.UncheckedException; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; import javax.annotation.Nullable; @@ -36,6 +37,11 @@ public Class getAnnotationType() { return Nested.class; } + @Override + public boolean isPropertyRelevant() { + return true; + } + @Override public boolean shouldVisit(PropertyVisitor visitor) { return !visitor.visitOutputFilePropertiesOnly(); @@ -57,6 +63,10 @@ public void visitPropertyValue(String propertyName, PropertyValue value, Propert } } + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + } + @Nullable private static Object unpackProvider(@Nullable Object value) { // Only unpack one level of Providers, since Provider> is not supported - we don't need two levels of laziness. diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/NoOpPropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/NoOpPropertyAnnotationHandler.java index 64c59ea531238..7a9de8c15837d 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/NoOpPropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/NoOpPropertyAnnotationHandler.java @@ -19,6 +19,7 @@ import org.gradle.api.internal.tasks.properties.BeanPropertyContext; import org.gradle.api.internal.tasks.properties.PropertyValue; import org.gradle.api.internal.tasks.properties.PropertyVisitor; +import org.gradle.internal.reflect.ParameterValidationContext; import org.gradle.internal.reflect.PropertyMetadata; import java.lang.annotation.Annotation; @@ -30,6 +31,11 @@ public NoOpPropertyAnnotationHandler(Class annotationType) this.annotationType = annotationType; } + @Override + public boolean isPropertyRelevant() { + return false; + } + public Class getAnnotationType() { return annotationType; } @@ -42,4 +48,8 @@ public boolean shouldVisit(PropertyVisitor visitor) { @Override public void visitPropertyValue(String propertyName, PropertyValue value, PropertyMetadata propertyMetadata, PropertyVisitor visitor, BeanPropertyContext context) { } + + @Override + public void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor) { + } } diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/PropertyAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/PropertyAnnotationHandler.java index a00d533abc456..ecf53ec5d736d 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/PropertyAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/PropertyAnnotationHandler.java @@ -15,18 +15,46 @@ */ package org.gradle.api.internal.tasks.properties.annotations; -import org.gradle.api.internal.tasks.properties.PropertyValueVisitor; +import org.gradle.api.internal.tasks.properties.BeanPropertyContext; +import org.gradle.api.internal.tasks.properties.PropertyValue; +import org.gradle.api.internal.tasks.properties.PropertyVisitor; +import org.gradle.internal.reflect.ParameterValidationContext; +import org.gradle.internal.reflect.PropertyMetadata; import java.lang.annotation.Annotation; /** * Handles validation, dependency handling, and skipping for a property marked with a given annotation. + * + *

Each handler must be registered as a global service.

*/ -public interface PropertyAnnotationHandler extends PropertyValueVisitor { +public interface PropertyAnnotationHandler { /** * The annotation type which this handler is responsible for. * * @return The annotation. */ Class getAnnotationType(); + + /** + * Does this handler do something useful with the properties that match it? Or can these properties be ignored? + * + * Should consider splitting up this type, perhaps into something that inspects the properties and produces the actual handlers and validation problems. + */ + boolean isPropertyRelevant(); + + /** + * Is the given visitor interested in this annotation? + */ + boolean shouldVisit(PropertyVisitor visitor); + + /** + * Visit the value of a property with this annotation attached. + */ + void visitPropertyValue(String propertyName, PropertyValue value, PropertyMetadata propertyMetadata, PropertyVisitor visitor, BeanPropertyContext context); + + /** + * Visits problems associated with the given property, if any. + */ + void validatePropertyMetadata(PropertyMetadata propertyMetadata, ParameterValidationContext visitor); } diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInjectAnnotationHandler.java b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/TypeAnnotationHandler.java similarity index 55% rename from subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInjectAnnotationHandler.java rename to subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/TypeAnnotationHandler.java index 3768b7be1ba29..973cb25e4c2a5 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInjectAnnotationHandler.java +++ b/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/annotations/TypeAnnotationHandler.java @@ -14,24 +14,22 @@ * limitations under the License. */ -package org.gradle.internal.instantiation; +package org.gradle.api.internal.tasks.properties.annotations; -import java.lang.annotation.Annotation; - -public class DefaultInjectAnnotationHandler implements InjectAnnotationHandler { - private final Class annotation; +import org.gradle.internal.reflect.ParameterValidationContext; - public DefaultInjectAnnotationHandler(Class annotation) { - this.annotation = annotation; - } +import java.lang.annotation.Annotation; - @Override - public String toString() { - return "handler for @" + annotation.getSimpleName(); - } +public interface TypeAnnotationHandler { + /** + * The annotation type which this handler is responsible for. + * + * @return The annotation. + */ + Class getAnnotationType(); - @Override - public Class getAnnotation() { - return annotation; - } + /** + * Visits problems associated with the given property, if any. + */ + void validateTypeMetadata(Class classWithAnnotationAttached, ParameterValidationContext visitor); } diff --git a/subprojects/core/src/main/java/org/gradle/api/tasks/bundling/AbstractArchiveTask.java b/subprojects/core/src/main/java/org/gradle/api/tasks/bundling/AbstractArchiveTask.java index 8b39a1249ce05..ca3a90fe0ec77 100644 --- a/subprojects/core/src/main/java/org/gradle/api/tasks/bundling/AbstractArchiveTask.java +++ b/subprojects/core/src/main/java/org/gradle/api/tasks/bundling/AbstractArchiveTask.java @@ -23,6 +23,7 @@ import org.gradle.api.file.RegularFileProperty; import org.gradle.api.internal.file.copy.CopyActionExecuter; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.model.ReplacedBy; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.AbstractCopyTask; @@ -107,7 +108,7 @@ private static String maybe(@Nullable String prefix, @Nullable String value) { * @deprecated Use {@link #getArchiveFileName()} */ @Deprecated - @Internal("Represented as part of archiveFile") + @ReplacedBy("archiveFileName") public String getArchiveName() { return archiveName.get(); } @@ -142,7 +143,7 @@ public Property getArchiveFileName() { * @deprecated Use {@link #getArchiveFile()} */ @Deprecated - @Internal("Represented as a part of the archiveFile") + @ReplacedBy("archiveFile") public File getArchivePath() { return getArchiveFile().get().getAsFile(); } @@ -175,8 +176,8 @@ public Provider getArchiveFile() { * @return the directory * @deprecated Use {@link #getDestinationDirectory()} */ - @Internal("Represented as part of archiveFile") @Deprecated + @ReplacedBy("destinationDirectory") public File getDestinationDir() { return archiveDestinationDirectory.getAsFile().get(); } @@ -188,7 +189,7 @@ public File getDestinationDir() { */ @Deprecated public void setDestinationDir(File destinationDir) { - archiveDestinationDirectory.set(destinationDir); + archiveDestinationDirectory.set(getProject().file(destinationDir)); } /** @@ -208,8 +209,8 @@ public DirectoryProperty getDestinationDirectory() { * @deprecated Use {@link #getArchiveBaseName()} */ @Nullable - @Internal("Represented as part of archiveFile") @Deprecated + @ReplacedBy("archiveBaseName") public String getBaseName() { return archiveBaseName.getOrNull(); } @@ -242,8 +243,8 @@ public Property getArchiveBaseName() { * @deprecated Use {@link #getArchiveAppendix()} */ @Nullable - @Internal("Represented as part of archiveFile") @Deprecated + @ReplacedBy("archiveAppendix") public String getAppendix() { return archiveAppendix.getOrNull(); } @@ -276,8 +277,8 @@ public Property getArchiveAppendix() { * @deprecated Use {@link #getArchiveVersion()} */ @Nullable - @Internal("Represented as part of archiveFile") @Deprecated + @ReplacedBy("archiveVersion") public String getVersion() { return archiveVersion.getOrNull(); } @@ -308,8 +309,8 @@ public Property getArchiveVersion() { * @deprecated Use {@link #getArchiveExtension()} */ @Nullable - @Internal("Represented as part of archiveFile") @Deprecated + @ReplacedBy("archiveExtension") public String getExtension() { return archiveExtension.getOrNull(); } @@ -340,8 +341,8 @@ public Property getArchiveExtension() { * @deprecated Use {@link #getArchiveClassifier()} */ @Nullable - @Internal("Represented as part of archiveFile") @Deprecated + @ReplacedBy("archiveClassifier") public String getClassifier() { return archiveClassifier.getOrNull(); } diff --git a/subprojects/core/src/main/java/org/gradle/cache/internal/DefaultCrossBuildInMemoryCacheFactory.java b/subprojects/core/src/main/java/org/gradle/cache/internal/DefaultCrossBuildInMemoryCacheFactory.java index 929d69cc03e28..4d1bb524cae06 100644 --- a/subprojects/core/src/main/java/org/gradle/cache/internal/DefaultCrossBuildInMemoryCacheFactory.java +++ b/subprojects/core/src/main/java/org/gradle/cache/internal/DefaultCrossBuildInMemoryCacheFactory.java @@ -16,7 +16,7 @@ package org.gradle.cache.internal; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Transformer; import org.gradle.initialization.SessionLifecycleListener; import org.gradle.internal.event.ListenerManager; diff --git a/subprojects/core/src/main/java/org/gradle/cache/internal/VersionSpecificCacheCleanupAction.java b/subprojects/core/src/main/java/org/gradle/cache/internal/VersionSpecificCacheCleanupAction.java index 8ac81b8cc5efe..8531b8812e971 100644 --- a/subprojects/core/src/main/java/org/gradle/cache/internal/VersionSpecificCacheCleanupAction.java +++ b/subprojects/core/src/main/java/org/gradle/cache/internal/VersionSpecificCacheCleanupAction.java @@ -21,14 +21,14 @@ import com.google.common.collect.SortedSetMultimap; import com.google.common.collect.TreeMultimap; import org.apache.commons.io.FileUtils; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.api.specs.Spec; import org.gradle.cache.CleanupProgressMonitor; import org.gradle.internal.time.Time; import org.gradle.internal.time.Timer; import org.gradle.util.GFileUtils; import org.gradle.util.GradleVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.io.File; @@ -41,7 +41,7 @@ public class VersionSpecificCacheCleanupAction implements DirectoryCleanupAction { @VisibleForTesting static final String MARKER_FILE_PATH = FILE_HASHES_CACHE_KEY + "/" + FILE_HASHES_CACHE_KEY + ".lock"; - private static final Logger LOGGER = Logging.getLogger(VersionSpecificCacheCleanupAction.class); + private static final Logger LOGGER = LoggerFactory.getLogger(VersionSpecificCacheCleanupAction.class); private static final long CLEANUP_INTERVAL_IN_HOURS = 24; private final VersionSpecificCacheDirectoryScanner versionSpecificCacheDirectoryScanner; diff --git a/subprojects/core/src/main/java/org/gradle/cache/internal/WrapperDistributionCleanupAction.java b/subprojects/core/src/main/java/org/gradle/cache/internal/WrapperDistributionCleanupAction.java index b51a2d37731c6..35567daba0cc9 100644 --- a/subprojects/core/src/main/java/org/gradle/cache/internal/WrapperDistributionCleanupAction.java +++ b/subprojects/core/src/main/java/org/gradle/cache/internal/WrapperDistributionCleanupAction.java @@ -26,11 +26,11 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.RegexFileFilter; import org.apache.commons.lang.StringUtils; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.cache.CleanupProgressMonitor; import org.gradle.internal.IoActions; import org.gradle.util.GradleVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -56,7 +56,7 @@ public class WrapperDistributionCleanupAction implements DirectoryCleanupAction { @VisibleForTesting static final String WRAPPER_DISTRIBUTION_FILE_PATH = "wrapper/dists"; - private static final Logger LOGGER = Logging.getLogger(WrapperDistributionCleanupAction.class); + private static final Logger LOGGER = LoggerFactory.getLogger(WrapperDistributionCleanupAction.class); private static final ImmutableMap JAR_FILE_PATTERNS_BY_PREFIX; private static final String BUILD_RECEIPT_ZIP_ENTRY_PATH = StringUtils.removeStart(GradleVersion.RESOURCE_NAME, "/"); diff --git a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/BuildCacheKeyInputs.java b/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/BuildCacheKeyInputs.java deleted file mode 100644 index ed68e865cf59d..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/BuildCacheKeyInputs.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.caching.internal.tasks; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; -import com.google.common.collect.ImmutableSortedSet; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.hash.HashCode; -import org.gradle.internal.snapshot.impl.ImplementationSnapshot; - -import javax.annotation.Nullable; -import java.util.List; - -/** - * Records the inputs which constitute the {@link TaskOutputCachingBuildCacheKey}. - */ -public class BuildCacheKeyInputs { - - private final ImplementationSnapshot taskImplementation; - private final List actionImplementations; - private final ImmutableSortedMap inputValueHashes; - private final ImmutableSortedMap inputFiles; - private final ImmutableSortedMap nonCacheableInputProperties; - private final ImmutableSortedSet outputPropertyNames; - - public BuildCacheKeyInputs( - @Nullable ImplementationSnapshot taskImplementation, - @Nullable ImmutableList actionImplementations, - @Nullable ImmutableSortedMap inputValueHashes, - @Nullable ImmutableSortedMap inputFiles, - @Nullable ImmutableSortedMap nonCacheableInputProperties, - @Nullable ImmutableSortedSet outputPropertyNames - ) { - this.taskImplementation = taskImplementation; - this.actionImplementations = actionImplementations; - this.inputValueHashes = inputValueHashes; - this.inputFiles = inputFiles; - this.nonCacheableInputProperties = nonCacheableInputProperties; - this.outputPropertyNames = outputPropertyNames; - } - - @Nullable - public ImplementationSnapshot getTaskImplementation() { - return taskImplementation; - } - - @Nullable - public List getActionImplementations() { - return actionImplementations; - } - - @Nullable - public ImmutableSortedMap getInputValueHashes() { - return inputValueHashes; - } - - @Nullable - public ImmutableSortedMap getInputFiles() { - return inputFiles; - } - - @Nullable - public ImmutableSortedMap getNonCacheableInputProperties() { - return nonCacheableInputProperties; - } - - @Nullable - public ImmutableSortedSet getOutputPropertyNames() { - return outputPropertyNames; - } - - @Override - public String toString() { - return "BuildCacheKeyInputs{" - + "taskImplementation=" + taskImplementation - + ", actionImplementations=" + actionImplementations - + ", inputValueHashes=" + inputValueHashes - + ", inputFiles=" + inputFiles - + ", nonCacheableInputProperties=" + nonCacheableInputProperties - + ", outputPropertyNames=" + outputPropertyNames - + '}'; - } -} diff --git a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DebuggingTaskOutputCachingBuildCacheKeyBuilder.java b/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DebuggingTaskOutputCachingBuildCacheKeyBuilder.java deleted file mode 100644 index 86b3f84556909..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DebuggingTaskOutputCachingBuildCacheKeyBuilder.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.caching.internal.tasks; - -import org.gradle.api.NonNullApi; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.hash.HashCode; -import org.gradle.internal.snapshot.impl.ImplementationSnapshot; - -import javax.annotation.Nullable; -import java.util.Collection; - -@NonNullApi -public class DebuggingTaskOutputCachingBuildCacheKeyBuilder implements TaskOutputCachingBuildCacheKeyBuilder { - private static final Logger LOGGER = Logging.getLogger(DebuggingTaskOutputCachingBuildCacheKeyBuilder.class); - - private final TaskOutputCachingBuildCacheKeyBuilder delegate; - - public DebuggingTaskOutputCachingBuildCacheKeyBuilder(TaskOutputCachingBuildCacheKeyBuilder delegate) { - this.delegate = delegate; - } - - @Override - public void appendTaskImplementation(ImplementationSnapshot taskImplementation) { - log("taskImplementation", taskImplementation); - delegate.appendTaskImplementation(taskImplementation); - } - - @Override - public void appendTaskActionImplementations(Collection taskActionImplementations) { - for (ImplementationSnapshot actionImpl : taskActionImplementations) { - log("actionImplementation", actionImpl); - } - delegate.appendTaskActionImplementations(taskActionImplementations); - } - - @Override - public void appendInputValuePropertyHash(String propertyName, HashCode hashCode) { - LOGGER.lifecycle("Appending inputValuePropertyHash for '{}' to build cache key: {}", propertyName, hashCode); - delegate.appendInputValuePropertyHash(propertyName, hashCode); - } - - @Override - public void appendInputFilesProperty(String propertyName, CurrentFileCollectionFingerprint fileCollectionFingerprint) { - LOGGER.lifecycle("Appending inputFilePropertyHash for '{}' to build cache key: {}", propertyName, fileCollectionFingerprint.getHash()); - delegate.appendInputFilesProperty(propertyName, fileCollectionFingerprint); - - } - - @Override - public void inputPropertyNotCacheable(String propertyName, String nonCacheableReason) { - LOGGER.lifecycle("Non-cacheable inputs: property '{}' {}.", propertyName, nonCacheableReason); - delegate.inputPropertyNotCacheable(propertyName, nonCacheableReason); - } - - @Override - public void appendOutputPropertyName(String propertyName) { - log("outputPropertyName", propertyName); - delegate.appendOutputPropertyName(propertyName); - } - - @Override - public TaskOutputCachingBuildCacheKey build() { - return delegate.build(); - } - - private void log(String name, @Nullable Object value) { - LOGGER.lifecycle("Appending {} to build cache key: {}", name, value); - } - -} diff --git a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DefaultTaskCacheKeyCalculator.java b/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DefaultTaskCacheKeyCalculator.java deleted file mode 100644 index b1883cb5aeef5..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DefaultTaskCacheKeyCalculator.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.caching.internal.tasks; - -import org.gradle.api.internal.TaskInternal; -import org.gradle.api.internal.tasks.properties.CacheableOutputFilePropertySpec; -import org.gradle.api.internal.tasks.properties.OutputFilePropertySpec; -import org.gradle.api.internal.tasks.properties.TaskProperties; -import org.gradle.internal.execution.history.BeforeExecutionState; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.hash.HashCode; -import org.gradle.internal.hash.Hasher; -import org.gradle.internal.hash.Hashing; -import org.gradle.internal.snapshot.ValueSnapshot; - -import java.util.Map; -import java.util.SortedMap; - -public class DefaultTaskCacheKeyCalculator implements TaskCacheKeyCalculator { - - public TaskOutputCachingBuildCacheKey calculate(TaskInternal task, BeforeExecutionState execution, TaskProperties properties, boolean buildCacheDebugLogging) { - TaskOutputCachingBuildCacheKeyBuilder builder = new DefaultTaskOutputCachingBuildCacheKeyBuilder(task.getIdentityPath()); - if (buildCacheDebugLogging) { - builder = new DebuggingTaskOutputCachingBuildCacheKeyBuilder(builder); - } - builder.appendTaskImplementation(execution.getImplementation()); - builder.appendTaskActionImplementations(execution.getAdditionalImplementations()); - - SortedMap inputProperties = execution.getInputProperties(); - for (Map.Entry entry : inputProperties.entrySet()) { - Hasher newHasher = Hashing.newHasher(); - entry.getValue().appendToHasher(newHasher); - if (newHasher.isValid()) { - HashCode hash = newHasher.hash(); - builder.appendInputValuePropertyHash(entry.getKey(), hash); - } else { - builder.inputPropertyNotCacheable(entry.getKey(), newHasher.getInvalidReason()); - } - } - - SortedMap inputFingerprints = execution.getInputFileProperties(); - for (Map.Entry entry : inputFingerprints.entrySet()) { - builder.appendInputFilesProperty(entry.getKey(), entry.getValue()); - } - - for (OutputFilePropertySpec propertySpec : properties.getOutputFileProperties()) { - if (!(propertySpec instanceof CacheableOutputFilePropertySpec)) { - continue; - } - if (((CacheableOutputFilePropertySpec) propertySpec).getOutputFile() != null) { - builder.appendOutputPropertyName(propertySpec.getPropertyName()); - } - } - - return builder.build(); - } -} diff --git a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DefaultTaskOutputCachingBuildCacheKeyBuilder.java b/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DefaultTaskOutputCachingBuildCacheKeyBuilder.java deleted file mode 100644 index a17ba93f849e9..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/DefaultTaskOutputCachingBuildCacheKeyBuilder.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.caching.internal.tasks; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSortedMap; -import com.google.common.collect.ImmutableSortedSet; -import org.gradle.api.NonNullApi; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.hash.HashCode; -import org.gradle.internal.hash.Hasher; -import org.gradle.internal.hash.Hashing; -import org.gradle.internal.snapshot.impl.ImplementationSnapshot; -import org.gradle.util.Path; - -import javax.annotation.Nullable; -import java.util.Collection; - -@NonNullApi -public class DefaultTaskOutputCachingBuildCacheKeyBuilder implements TaskOutputCachingBuildCacheKeyBuilder { - - private final Hasher hasher = Hashing.newHasher(); - private final Path taskPath; - private ImplementationSnapshot taskImplementation; - private ImmutableList actionImplementations; - private final ImmutableSortedMap.Builder inputValueHashes = ImmutableSortedMap.naturalOrder(); - private final ImmutableSortedMap.Builder inputFiles = ImmutableSortedMap.naturalOrder(); - private final ImmutableSortedMap.Builder nonCacheableInputProperties = ImmutableSortedMap.naturalOrder(); - private final ImmutableSortedSet.Builder outputPropertyNames = ImmutableSortedSet.naturalOrder(); - - public DefaultTaskOutputCachingBuildCacheKeyBuilder(Path taskPath) { - this.taskPath = taskPath; - } - - @Override - public void appendTaskImplementation(ImplementationSnapshot taskImplementation) { - this.taskImplementation = taskImplementation; - taskImplementation.appendToHasher(hasher); - } - - @Override - public void appendTaskActionImplementations(Collection taskActionImplementations) { - ImmutableList.Builder builder = ImmutableList.builder(); - for (ImplementationSnapshot actionImpl : taskActionImplementations) { - builder.add(actionImpl); - actionImpl.appendToHasher(hasher); - } - - this.actionImplementations = builder.build(); - } - - @Override - public void appendInputValuePropertyHash(String propertyName, HashCode hashCode) { - hasher.putString(propertyName); - hasher.putHash(hashCode); - inputValueHashes.put(propertyName, hashCode); - } - - @Override - public void appendInputFilesProperty(String propertyName, CurrentFileCollectionFingerprint fileCollectionFingerprint) { - hasher.putString(propertyName); - hasher.putHash(fileCollectionFingerprint.getHash()); - inputFiles.put(propertyName, fileCollectionFingerprint); - } - - @Override - public void inputPropertyNotCacheable(String propertyName, String nonCacheableReason) { - hasher.markAsInvalid(nonCacheableReason); - nonCacheableInputProperties.put(propertyName, nonCacheableReason); - } - - @Override - public void appendOutputPropertyName(String propertyName) { - outputPropertyNames.add(propertyName); - hasher.putString(propertyName); - } - - @Override - public TaskOutputCachingBuildCacheKey build() { - BuildCacheKeyInputs inputs = new BuildCacheKeyInputs(taskImplementation, actionImplementations, inputValueHashes.build(), inputFiles.build(), nonCacheableInputProperties.build(), outputPropertyNames.build()); - HashCode hash; - if (!hasher.isValid()) { - hash = null; - } else { - hash = hasher.hash(); - } - return new DefaultTaskOutputCachingBuildCacheKey(taskPath, hash, inputs); - } - - private static class DefaultTaskOutputCachingBuildCacheKey implements TaskOutputCachingBuildCacheKey { - - private final Path taskPath; - private final HashCode hashCode; - private final BuildCacheKeyInputs inputs; - - private DefaultTaskOutputCachingBuildCacheKey(Path taskPath, @Nullable HashCode hashCode, BuildCacheKeyInputs inputs) { - this.taskPath = taskPath; - this.hashCode = hashCode; - this.inputs = inputs; - } - - @Override - public Path getTaskPath() { - return taskPath; - } - - @Override - public String getHashCode() { - return Preconditions.checkNotNull(hashCode, "Cannot determine hash code for invalid build cache key").toString(); - } - - @Override - public BuildCacheKeyInputs getInputs() { - return inputs; - } - - @Override - public byte[] getHashCodeBytes() { - return hashCode == null ? null : hashCode.toByteArray(); - } - - @Override - public boolean isValid() { - return hashCode != null; - } - - @Override - public String getDisplayName() { - if (hashCode == null) { - return "INVALID cache key for task '" + taskPath + "'"; - } - return hashCode + " for task '" + taskPath + "'"; - } - - @Override - public String toString() { - return String.valueOf(hashCode); - } - } -} diff --git a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskOutputCachingBuildCacheKeyBuilder.java b/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskOutputCachingBuildCacheKeyBuilder.java deleted file mode 100644 index b428159403fd2..0000000000000 --- a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskOutputCachingBuildCacheKeyBuilder.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.caching.internal.tasks; - -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.hash.HashCode; -import org.gradle.internal.snapshot.impl.ImplementationSnapshot; - -import java.util.Collection; - -public interface TaskOutputCachingBuildCacheKeyBuilder { - void appendTaskImplementation(ImplementationSnapshot taskImplementation); - - void appendTaskActionImplementations(Collection taskActionImplementations); - - void appendInputValuePropertyHash(String propertyName, HashCode hashCode); - - void appendInputFilesProperty(String propertyName, CurrentFileCollectionFingerprint fingerprint); - - void appendOutputPropertyName(String propertyName); - - TaskOutputCachingBuildCacheKey build(); - - void inputPropertyNotCacheable(String propertyName, String nonCacheableReason); -} diff --git a/subprojects/core/src/main/java/org/gradle/deployment/internal/DeploymentRegistry.java b/subprojects/core/src/main/java/org/gradle/deployment/internal/DeploymentRegistry.java index fdc280a65c07f..9b071cc504afb 100644 --- a/subprojects/core/src/main/java/org/gradle/deployment/internal/DeploymentRegistry.java +++ b/subprojects/core/src/main/java/org/gradle/deployment/internal/DeploymentRegistry.java @@ -16,7 +16,7 @@ package org.gradle.deployment.internal; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import javax.annotation.Nullable; diff --git a/subprojects/core/src/main/java/org/gradle/execution/ProjectExecutionServiceRegistry.java b/subprojects/core/src/main/java/org/gradle/execution/ProjectExecutionServiceRegistry.java index 7085203bd9bc7..9ef185f824a23 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/ProjectExecutionServiceRegistry.java +++ b/subprojects/core/src/main/java/org/gradle/execution/ProjectExecutionServiceRegistry.java @@ -20,6 +20,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.internal.service.ServiceRegistry; import javax.annotation.Nonnull; @@ -36,7 +37,11 @@ public ProjectExecutionServices load(@Nonnull ProjectInternal project) { }); public T getProjectService(ProjectInternal project, Class serviceType) { - return projectRegistries.getUnchecked(project).get(serviceType); + return forProject(project).get(serviceType); + } + + public ServiceRegistry forProject(ProjectInternal project) { + return projectRegistries.getUnchecked(project); } @Override diff --git a/subprojects/core/src/main/java/org/gradle/execution/ProjectExecutionServices.java b/subprojects/core/src/main/java/org/gradle/execution/ProjectExecutionServices.java index 81ae79c5e0bda..88cac6bf91035 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/ProjectExecutionServices.java +++ b/subprojects/core/src/main/java/org/gradle/execution/ProjectExecutionServices.java @@ -30,37 +30,35 @@ import org.gradle.api.internal.tasks.TaskExecuter; import org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter; import org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter; +import org.gradle.api.internal.tasks.execution.DefaultTaskCacheabilityResolver; import org.gradle.api.internal.tasks.execution.DefaultTaskFingerprinter; import org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter; import org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter; import org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter; -import org.gradle.api.internal.tasks.execution.FinishSnapshotTaskInputsBuildOperationTaskExecuter; import org.gradle.api.internal.tasks.execution.ResolveAfterPreviousExecutionStateTaskExecuter; import org.gradle.api.internal.tasks.execution.ResolveBeforeExecutionOutputsTaskExecuter; import org.gradle.api.internal.tasks.execution.ResolveBeforeExecutionStateTaskExecuter; -import org.gradle.api.internal.tasks.execution.ResolveBuildCacheKeyExecuter; -import org.gradle.api.internal.tasks.execution.ResolveIncrementalChangesTaskExecuter; import org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter; -import org.gradle.api.internal.tasks.execution.ResolveTaskOutputCachingStateExecuter; import org.gradle.api.internal.tasks.execution.SkipEmptySourceFilesTaskExecuter; import org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter; import org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter; import org.gradle.api.internal.tasks.execution.StartSnapshotTaskInputsBuildOperationTaskExecuter; +import org.gradle.api.internal.tasks.execution.TaskCacheabilityResolver; import org.gradle.api.internal.tasks.execution.TaskFingerprinter; import org.gradle.api.internal.tasks.execution.ValidatingTaskExecuter; import org.gradle.api.internal.tasks.properties.PropertyWalker; import org.gradle.caching.internal.controller.BuildCacheController; -import org.gradle.caching.internal.tasks.DefaultTaskCacheKeyCalculator; -import org.gradle.caching.internal.tasks.TaskCacheKeyCalculator; import org.gradle.execution.taskgraph.TaskExecutionGraphInternal; import org.gradle.internal.classloader.ClassLoaderHierarchyHasher; import org.gradle.internal.cleanup.BuildOutputCleanupRegistry; import org.gradle.internal.event.ListenerManager; +import org.gradle.internal.execution.CachingResult; +import org.gradle.internal.execution.IncrementalContext; import org.gradle.internal.execution.OutputChangeListener; import org.gradle.internal.execution.WorkExecutor; import org.gradle.internal.execution.history.ExecutionHistoryStore; import org.gradle.internal.execution.history.OutputFilesRepository; -import org.gradle.internal.execution.impl.steps.UpToDateResult; +import org.gradle.internal.file.RelativeFilePathResolver; import org.gradle.internal.fingerprint.FileCollectionFingerprinter; import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry; import org.gradle.internal.fingerprint.classpath.ClasspathFingerprinter; @@ -85,6 +83,10 @@ TaskActionListener createTaskActionListener(ListenerManager listenerManager) { return listenerManager.getBroadcaster(TaskActionListener.class); } + TaskCacheabilityResolver createTaskCacheabilityResolver(RelativeFilePathResolver relativeFilePathResolver) { + return new DefaultTaskCacheabilityResolver(relativeFilePathResolver); + } + TaskExecuter createTaskExecuter(TaskExecutionModeResolver repository, BuildCacheController buildCacheController, TaskInputsListener inputsListener, @@ -104,33 +106,24 @@ TaskExecuter createTaskExecuter(TaskExecutionModeResolver repository, PropertyWalker propertyWalker, TaskExecutionGraphInternal taskExecutionGraph, TaskExecutionListener taskExecutionListener, - WorkExecutor workExecutor + TaskCacheabilityResolver taskCacheabilityResolver, + WorkExecutor workExecutor ) { boolean buildCacheEnabled = buildCacheController.isEnabled(); boolean scanPluginApplied = buildScanPlugin.isBuildScanPluginApplied(); - TaskCacheKeyCalculator cacheKeyCalculator = new DefaultTaskCacheKeyCalculator(); TaskExecuter executer = new ExecuteActionsTaskExecuter( buildCacheEnabled, + scanPluginApplied, taskFingerprinter, executionHistoryStore, - outputFilesRepository, buildOperationExecutor, asyncWorkTracker, actionListener, + taskCacheabilityResolver, workExecutor ); - executer = new ResolveIncrementalChangesTaskExecuter(executer); - executer = new ResolveTaskOutputCachingStateExecuter(buildCacheEnabled, resolver, executer); - // TODO:lptr this should be added only if the scan plugin is applied, but SnapshotTaskInputsOperationIntegrationTest - // TODO:lptr expects it to be added also when the build cache is enabled (but not the scan plugin) - if (buildCacheEnabled || scanPluginApplied) { - executer = new FinishSnapshotTaskInputsBuildOperationTaskExecuter(executer); - } - if (buildCacheEnabled || scanPluginApplied) { - executer = new ResolveBuildCacheKeyExecuter(cacheKeyCalculator, buildCacheController.isEmitDebugLogging(), executer); - } executer = new ResolveBeforeExecutionStateTaskExecuter(classLoaderHierarchyHasher, valueSnapshotter, taskFingerprinter, executer); executer = new ValidatingTaskExecuter(executer); executer = new SkipEmptySourceFilesTaskExecuter(inputsListener, executionHistoryStore, cleanupRegistry, outputChangeListener, executer); diff --git a/subprojects/core/src/main/java/org/gradle/execution/plan/ActionNode.java b/subprojects/core/src/main/java/org/gradle/execution/plan/ActionNode.java index e5880ddff4f2a..392bce2152150 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/plan/ActionNode.java +++ b/subprojects/core/src/main/java/org/gradle/execution/plan/ActionNode.java @@ -18,7 +18,10 @@ import org.gradle.api.Action; import org.gradle.api.Project; +import org.gradle.api.internal.project.ProjectInternal; import org.gradle.api.internal.tasks.WorkNodeAction; +import org.gradle.execution.ProjectExecutionServiceRegistry; +import org.gradle.internal.service.ServiceRegistry; import javax.annotation.Nullable; import java.util.Collections; @@ -59,6 +62,11 @@ public boolean isPublicNode() { return false; } + @Override + public boolean requiresMonitoring() { + return false; + } + @Override public String toString() { return "work action " + action; @@ -75,7 +83,9 @@ public Project getProject() { return action.getProject(); } - public void run() { - action.run(); + public void run(ProjectExecutionServiceRegistry services) { + ProjectInternal project = (ProjectInternal) action.getProject(); + ServiceRegistry registry = project == null ? ServiceRegistry.EMPTY : services.forProject(project); + action.run(registry); } } diff --git a/subprojects/core/src/main/java/org/gradle/execution/plan/DefaultExecutionPlan.java b/subprojects/core/src/main/java/org/gradle/execution/plan/DefaultExecutionPlan.java index 19af2f79012e4..a63461e964b20 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/plan/DefaultExecutionPlan.java +++ b/subprojects/core/src/main/java/org/gradle/execution/plan/DefaultExecutionPlan.java @@ -75,6 +75,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Deque; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -103,7 +104,8 @@ public class DefaultExecutionPlan implements ExecutionPlan { private final Map mutations = Maps.newIdentityHashMap(); private final Map canonicalizedFileCache = Maps.newIdentityHashMap(); private final Map, Boolean> reachableCache = Maps.newHashMap(); - private final Set dependenciesCompleteCache = Sets.newHashSet(); + private final List dependenciesWhichRequireMonitoring = Lists.newArrayList(); + private boolean maybeNodesReady; private final WorkerLeaseService workerLeaseService; private final GradleInternal gradle; @@ -258,6 +260,7 @@ public NodeInVisitingSegment apply(TaskNode taskNode) { } })); int visitingSegmentCounter = nodeQueue.size(); + Set dependenciesWhichRequireMonitoring = Sets.newHashSet(); HashMultimap visitingNodes = HashMultimap.create(); Deque walkedShouldRunAfterEdges = new ArrayDeque(); @@ -269,10 +272,13 @@ public NodeInVisitingSegment apply(TaskNode taskNode) { int currentSegment = nodeInVisitingSegment.visitingSegment; Node node = nodeInVisitingSegment.node; - if (node.isIncludeInGraph() || nodeMapping.contains(node)) { + if (!node.isIncludeInGraph() || nodeMapping.contains(node)) { nodeQueue.remove(0); visitingNodes.remove(node, currentSegment); maybeRemoveProcessedShouldRunAfterEdge(walkedShouldRunAfterEdges, node); + if (node.requiresMonitoring()) { + dependenciesWhichRequireMonitoring.add(node); + } continue; } @@ -313,6 +319,9 @@ public NodeInVisitingSegment apply(TaskNode taskNode) { visitingNodes.remove(node, currentSegment); path.pop(); nodeMapping.add(node); + if (node.requiresMonitoring()) { + dependenciesWhichRequireMonitoring.add(node); + } MutationInfo mutations = getOrCreateMutationsOf(node); for (Node dependency : node.getDependencySuccessors()) { @@ -335,6 +344,10 @@ public NodeInVisitingSegment apply(TaskNode taskNode) { } executionQueue.clear(); Iterables.addAll(executionQueue, nodeMapping); + for (Node node : executionQueue) { + maybeNodesReady |= node.updateAllDependenciesComplete() && node.isReady(); + } + this.dependenciesWhichRequireMonitoring.addAll(dependenciesWhichRequireMonitoring); } private MutationInfo getOrCreateMutationsOf(Node node) { @@ -505,7 +518,7 @@ public void clear() { mutations.clear(); canonicalizedFileCache.clear(); reachableCache.clear(); - dependenciesCompleteCache.clear(); + dependenciesWhichRequireMonitoring.clear(); runningNodes.clear(); } @@ -540,10 +553,22 @@ public Node selectNext(WorkerLeaseRegistry.WorkerLease workerLease, ResourceLock return null; } + for (Iterator iterator = dependenciesWhichRequireMonitoring.iterator(); iterator.hasNext();) { + Node node = iterator.next(); + if (node.isComplete()) { + updateAllDependenciesCompleteForPredecessors(node); + iterator.remove(); + } + } + if (!maybeNodesReady) { + return null; + } Iterator iterator = executionQueue.iterator(); + boolean foundReadyNode = false; while (iterator.hasNext()) { Node node = iterator.next(); - if (node.isReady() && allDependenciesComplete(node)) { + if (node.isReady() && node.allDependenciesComplete()) { + foundReadyNode = true; MutationInfo mutations = getResolvedMutationInfo(node); // TODO: convert output file checks to a resource lock @@ -559,15 +584,22 @@ public Node selectNext(WorkerLeaseRegistry.WorkerLease workerLease, ResourceLock node.startExecution(); } else { node.skipExecution(); + updateAllDependenciesCompleteForPredecessors(node); } iterator.remove(); - return node; } } + maybeNodesReady = foundReadyNode; return null; } + private void updateAllDependenciesCompleteForPredecessors(Node node) { + for (Node predecessor : node.getAllPredecessors()) { + maybeNodesReady |= predecessor.updateAllDependenciesComplete() && predecessor.isReady(); + } + } + private boolean tryLockProjectFor(Node node) { if (node.getProject() != null) { return getProjectLock(node.getProject()).tryLock(); @@ -648,7 +680,7 @@ public void run() { } @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { mutations.hasFileInputs = true; } }); @@ -680,19 +712,6 @@ private void withDeadlockHandling(TaskNode task, String singular, String descrip } } - private boolean allDependenciesComplete(Node node) { - if (dependenciesCompleteCache.contains(node)) { - return true; - } - - boolean dependenciesComplete = node.allDependenciesComplete(); - if (dependenciesComplete) { - dependenciesCompleteCache.add(node); - } - - return dependenciesComplete; - } - private boolean allProjectsLocked() { for (ResourceLock lock : projectLocks.values()) { if (!lock.isLocked()) { @@ -863,6 +882,7 @@ private void recordNodeCompleted(Node node) { if (canRemoveMutation(mutations)) { this.mutations.remove(node); } + updateAllDependenciesCompleteForPredecessors(node); } private static boolean canRemoveMutation(@Nullable MutationInfo mutations) { @@ -874,6 +894,7 @@ public void nodeComplete(Node node) { try { if (!node.isComplete()) { enforceFinalizers(node); + maybeNodesReady = true; if (node.isFailed()) { handleFailure(node); } @@ -889,7 +910,8 @@ public void nodeComplete(Node node) { private static void enforceFinalizers(Node node) { for (Node finalizerNode : node.getFinalizers()) { if (finalizerNode.isRequired() || finalizerNode.isMustNotRun()) { - enforceWithDependencies(finalizerNode, Sets.newHashSet()); + HashSet enforcedNodes = Sets.newHashSet(); + enforceWithDependencies(finalizerNode, enforcedNodes); } } } @@ -907,6 +929,10 @@ private static void enforceWithDependencies(Node nodeInfo, Set enforcedNod if (node.isMustNotRun() || node.isRequired()) { node.enforceRun(); + // Completed changed from true to false - inform all nodes depending on this one. + for (Node predecessor : node.getAllPredecessors()) { + predecessor.forceAllDependenciesCompleteUpdate(); + } } } } @@ -963,6 +989,7 @@ private boolean abortExecution(boolean abortAll) { node.abortExecution(); aborted = true; } + updateAllDependenciesCompleteForPredecessors(node); } return aborted; } diff --git a/subprojects/core/src/main/java/org/gradle/execution/plan/DefaultPlanExecutor.java b/subprojects/core/src/main/java/org/gradle/execution/plan/DefaultPlanExecutor.java index 5c293a9dd718a..7d8e8170477ec 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/plan/DefaultPlanExecutor.java +++ b/subprojects/core/src/main/java/org/gradle/execution/plan/DefaultPlanExecutor.java @@ -19,8 +19,6 @@ import org.gradle.api.Action; import org.gradle.api.NonNullApi; import org.gradle.api.Transformer; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.concurrent.ParallelismConfiguration; import org.gradle.initialization.BuildCancellationToken; import org.gradle.internal.MutableBoolean; @@ -34,6 +32,8 @@ import org.gradle.internal.time.Timer; import org.gradle.internal.work.WorkerLeaseRegistry.WorkerLease; import org.gradle.internal.work.WorkerLeaseService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.concurrent.Executor; @@ -45,7 +45,7 @@ @NonNullApi public class DefaultPlanExecutor implements PlanExecutor { - private static final Logger LOGGER = Logging.getLogger(DefaultPlanExecutor.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultPlanExecutor.class); private final int executorCount; private final ExecutorFactory executorFactory; private final WorkerLeaseService workerLeaseService; diff --git a/subprojects/core/src/main/java/org/gradle/execution/plan/LocalTaskNode.java b/subprojects/core/src/main/java/org/gradle/execution/plan/LocalTaskNode.java index c1e73c3746a2b..c21c5ecc3fc56 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/plan/LocalTaskNode.java +++ b/subprojects/core/src/main/java/org/gradle/execution/plan/LocalTaskNode.java @@ -88,13 +88,18 @@ public void resolveDependencies(TaskDependencyResolver dependencyResolver, Actio processHardSuccessor.execute(targetNode); } for (Node targetNode : getMustRunAfter(dependencyResolver)) { - addMustSuccessor(targetNode); + addMustSuccessor((TaskNode) targetNode); } for (Node targetNode : getShouldRunAfter(dependencyResolver)) { addShouldSuccessor(targetNode); } } + @Override + public boolean requiresMonitoring() { + return false; + } + private void addFinalizerNode(TaskNode finalizerNode) { addFinalizer(finalizerNode); if (!finalizerNode.isInKnownState()) { diff --git a/subprojects/core/src/main/java/org/gradle/execution/plan/Node.java b/subprojects/core/src/main/java/org/gradle/execution/plan/Node.java index 655268aecc074..61ea78decedf5 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/plan/Node.java +++ b/subprojects/core/src/main/java/org/gradle/execution/plan/Node.java @@ -38,6 +38,7 @@ enum ExecutionState { private ExecutionState state; private boolean dependenciesProcessed; + private boolean allDependenciesComplete; private Throwable executionFailure; private final NavigableSet dependencySuccessors = Sets.newTreeSet(); private final NavigableSet dependencyPredecessors = Sets.newTreeSet(); @@ -60,7 +61,7 @@ public boolean isMustNotRun() { } public boolean isIncludeInGraph() { - return state == ExecutionState.NOT_REQUIRED || state == ExecutionState.UNKNOWN; + return state != ExecutionState.NOT_REQUIRED && state != ExecutionState.UNKNOWN; } public boolean isReady() { @@ -168,7 +169,7 @@ protected void addDependencySuccessor(Node toNode) { } @OverridingMethodsMustInvokeSuper - public boolean allDependenciesComplete() { + protected boolean doCheckDependenciesComplete() { for (Node dependency : dependencySuccessors) { if (!dependency.isComplete()) { return false; @@ -178,6 +179,25 @@ public boolean allDependenciesComplete() { return true; } + /** + * Returns if all dependencies completed, but have not been completed in the last check. + */ + public boolean updateAllDependenciesComplete() { + if (!allDependenciesComplete) { + forceAllDependenciesCompleteUpdate(); + return allDependenciesComplete; + } + return false; + } + + public void forceAllDependenciesCompleteUpdate() { + allDependenciesComplete = doCheckDependenciesComplete(); + } + + public boolean allDependenciesComplete() { + return allDependenciesComplete; + } + public boolean allDependenciesSuccessful() { for (Node dependency : dependencySuccessors) { if (!dependency.isSuccessful()) { @@ -187,6 +207,11 @@ public boolean allDependenciesSuccessful() { return true; } + @OverridingMethodsMustInvokeSuper + protected Iterable getAllPredecessors() { + return getDependencyPredecessors(); + } + public abstract void prepareForExecution(); public abstract void resolveDependencies(TaskDependencyResolver dependencyResolver, Action processHardSuccessor); @@ -221,6 +246,13 @@ public boolean hasHardSuccessor(Node successor) { public abstract boolean isPublicNode(); + /** + * Whether the task needs to be queried if it is completed. + * + * Everything where the value of {@link #isComplete()} depends on some other state, like another task in an included build. + */ + public abstract boolean requiresMonitoring(); + /** * Returns the project which the node requires access to, if any. * diff --git a/subprojects/core/src/main/java/org/gradle/execution/plan/TaskNode.java b/subprojects/core/src/main/java/org/gradle/execution/plan/TaskNode.java index cc1c0f20cf77c..3104af2fa2c03 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/plan/TaskNode.java +++ b/subprojects/core/src/main/java/org/gradle/execution/plan/TaskNode.java @@ -28,13 +28,14 @@ public abstract class TaskNode extends Node { private final NavigableSet mustSuccessors = Sets.newTreeSet(); + private final Set mustPredecessors = Sets.newHashSet(); private final NavigableSet shouldSuccessors = Sets.newTreeSet(); private final NavigableSet finalizers = Sets.newTreeSet(); private final NavigableSet finalizingSuccessors = Sets.newTreeSet(); @Override - public boolean allDependenciesComplete() { - if (!super.allDependenciesComplete()) { + public boolean doCheckDependenciesComplete() { + if (!super.doCheckDependenciesComplete()) { return false; } for (Node dependency : mustSuccessors) { @@ -68,8 +69,9 @@ public Set getShouldSuccessors() { return shouldSuccessors; } - protected void addMustSuccessor(Node toNode) { + protected void addMustSuccessor(TaskNode toNode) { mustSuccessors.add(toNode); + toNode.mustPredecessors.add(this); } protected void addFinalizingSuccessor(TaskNode finalized) { @@ -103,6 +105,11 @@ public Iterable getAllSuccessorsInReverseOrder() { ); } + @Override + public Iterable getAllPredecessors() { + return Iterables.concat(mustPredecessors, finalizers, super.getAllPredecessors()); + } + @Override public boolean hasHardSuccessor(Node successor) { if (super.hasHardSuccessor(successor)) { diff --git a/subprojects/core/src/main/java/org/gradle/execution/plan/TaskNodeFactory.java b/subprojects/core/src/main/java/org/gradle/execution/plan/TaskNodeFactory.java index 4789dcb83141a..29def83f4ab8e 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/plan/TaskNodeFactory.java +++ b/subprojects/core/src/main/java/org/gradle/execution/plan/TaskNodeFactory.java @@ -127,6 +127,11 @@ public void prepareForExecution() { public void resolveDependencies(TaskDependencyResolver dependencyResolver, Action processHardSuccessor) { } + @Override + public boolean requiresMonitoring() { + return true; + } + @Override public void require() { // Ignore diff --git a/subprojects/core/src/main/java/org/gradle/execution/plan/WorkNodeExecutor.java b/subprojects/core/src/main/java/org/gradle/execution/plan/WorkNodeExecutor.java index 2c141e085ab8a..e1bacde4d27d1 100644 --- a/subprojects/core/src/main/java/org/gradle/execution/plan/WorkNodeExecutor.java +++ b/subprojects/core/src/main/java/org/gradle/execution/plan/WorkNodeExecutor.java @@ -24,7 +24,7 @@ public boolean execute(Node node, ProjectExecutionServiceRegistry services) { if (!(node instanceof ActionNode)) { return false; } - ((ActionNode) node).run(); + ((ActionNode) node).run(services); return true; } } diff --git a/subprojects/core/src/main/java/org/gradle/groovy/scripts/DefaultScript.java b/subprojects/core/src/main/java/org/gradle/groovy/scripts/DefaultScript.java index c9eb16a624962..997b11894c721 100755 --- a/subprojects/core/src/main/java/org/gradle/groovy/scripts/DefaultScript.java +++ b/subprojects/core/src/main/java/org/gradle/groovy/scripts/DefaultScript.java @@ -97,11 +97,13 @@ public void init(final Object target, ServiceRegistry services) { File sourceFile = getScriptSource().getResource().getLocation().getFile(); if (sourceFile != null) { FileResolver resolver = fileLookup.getFileResolver(sourceFile.getParentFile()); - fileOperations = new DefaultFileOperations(resolver, null, null, instantiator, fileLookup, directoryFileTreeFactory, streamHasher, fileHasher, textResourceLoader, new DefaultFileCollectionFactory(resolver, null), fileSystem, clock); + DefaultFileCollectionFactory fileCollectionFactoryWithBase = new DefaultFileCollectionFactory(resolver, null); + fileOperations = new DefaultFileOperations(resolver, null, null, instantiator, fileLookup, directoryFileTreeFactory, streamHasher, fileHasher, textResourceLoader, fileCollectionFactoryWithBase, fileSystem, clock); + processOperations = services.get(ExecFactory.class).forContext(resolver, fileCollectionFactoryWithBase, instantiator); } else { fileOperations = new DefaultFileOperations(fileLookup.getFileResolver(), null, null, instantiator, fileLookup, directoryFileTreeFactory, streamHasher, fileHasher, textResourceLoader, fileCollectionFactory, fileSystem, clock); + processOperations = services.get(ExecFactory.class); } - processOperations = services.get(ExecFactory.class); } providerFactory = services.get(ProviderFactory.class); diff --git a/subprojects/core/src/main/java/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporter.java b/subprojects/core/src/main/java/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporter.java index bd6588e3352df..b35e72efa08af 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporter.java +++ b/subprojects/core/src/main/java/org/gradle/internal/featurelifecycle/ScriptUsageLocationReporter.java @@ -16,7 +16,7 @@ package org.gradle.internal.featurelifecycle; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.lang.StringUtils; import org.gradle.groovy.scripts.Script; import org.gradle.groovy.scripts.ScriptExecutionListener; diff --git a/subprojects/core/src/main/java/org/gradle/internal/file/JarCache.java b/subprojects/core/src/main/java/org/gradle/internal/file/JarCache.java index ce18e6e02a7b2..4d285fb5beff8 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/file/JarCache.java +++ b/subprojects/core/src/main/java/org/gradle/internal/file/JarCache.java @@ -16,7 +16,7 @@ package org.gradle.internal.file; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.Factory; import org.gradle.internal.hash.FileHasher; import org.gradle.internal.hash.HashCode; diff --git a/subprojects/core/src/main/java/org/gradle/internal/filewatch/FileWatcher.java b/subprojects/core/src/main/java/org/gradle/internal/filewatch/FileWatcher.java index a5ce92090780e..3ec9a03b8bd7c 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/filewatch/FileWatcher.java +++ b/subprojects/core/src/main/java/org/gradle/internal/filewatch/FileWatcher.java @@ -16,7 +16,7 @@ package org.gradle.internal.filewatch; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.internal.file.FileSystemSubset; import org.gradle.internal.concurrent.Stoppable; diff --git a/subprojects/core/src/main/java/org/gradle/internal/filewatch/SingleFirePendingChangesListener.java b/subprojects/core/src/main/java/org/gradle/internal/filewatch/SingleFirePendingChangesListener.java index 11b5e0ff92bad..4aa7f48da7874 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/filewatch/SingleFirePendingChangesListener.java +++ b/subprojects/core/src/main/java/org/gradle/internal/filewatch/SingleFirePendingChangesListener.java @@ -16,11 +16,11 @@ package org.gradle.internal.filewatch; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class SingleFirePendingChangesListener implements PendingChangesListener { - private static final Logger LOGGER = Logging.getLogger(SingleFirePendingChangesListener.class); + private static final Logger LOGGER = LoggerFactory.getLogger(SingleFirePendingChangesListener.class); private final PendingChangesListener delegate; private boolean seenChanges; diff --git a/subprojects/core/src/main/java/org/gradle/internal/filewatch/jdk7/WatchPointsRegistry.java b/subprojects/core/src/main/java/org/gradle/internal/filewatch/jdk7/WatchPointsRegistry.java index 1dfdea749c849..543d38a776994 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/filewatch/jdk7/WatchPointsRegistry.java +++ b/subprojects/core/src/main/java/org/gradle/internal/filewatch/jdk7/WatchPointsRegistry.java @@ -23,14 +23,14 @@ import com.google.common.collect.Iterables; import org.gradle.api.internal.file.FileSystemSubset; import org.gradle.api.internal.file.ImmutableDirectoryTree; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.FileUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; class WatchPointsRegistry { - private final static Logger LOG = Logging.getLogger(WatchPointsRegistry.class); + private final static Logger LOG = LoggerFactory.getLogger(WatchPointsRegistry.class); private final CombinedRootSubset rootSubset = new CombinedRootSubset(); private ImmutableSet allRequestedRoots; private final boolean createNewStartingPointsUnderExistingRoots; diff --git a/subprojects/core/src/main/java/org/gradle/internal/filewatch/jdk7/WatchServiceRegistrar.java b/subprojects/core/src/main/java/org/gradle/internal/filewatch/jdk7/WatchServiceRegistrar.java index 6f2e18c3833bd..173ef21692903 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/filewatch/jdk7/WatchServiceRegistrar.java +++ b/subprojects/core/src/main/java/org/gradle/internal/filewatch/jdk7/WatchServiceRegistrar.java @@ -19,13 +19,13 @@ import com.google.common.base.Throwables; import org.gradle.api.JavaVersion; import org.gradle.api.internal.file.FileSystemSubset; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.UncheckedException; import org.gradle.internal.filewatch.FileWatcher; import org.gradle.internal.filewatch.FileWatcherEvent; import org.gradle.internal.filewatch.FileWatcherListener; import org.gradle.internal.os.OperatingSystem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -48,7 +48,7 @@ import java.util.concurrent.locks.ReentrantLock; class WatchServiceRegistrar implements FileWatcherListener { - private final static Logger LOG = Logging.getLogger(WatchServiceRegistrar.class); + private final static Logger LOG = LoggerFactory.getLogger(WatchServiceRegistrar.class); private static final boolean FILE_TREE_WATCHING_SUPPORTED = OperatingSystem.current().isWindows() && !JavaVersion.current().isJava9Compatible(); private static final WatchEvent.Modifier[] WATCH_MODIFIERS = instantiateWatchModifiers(); private static final WatchEvent.Kind[] WATCH_KINDS = new WatchEvent.Kind[]{StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY}; diff --git a/subprojects/core/src/main/java/org/gradle/internal/fingerprint/classpath/impl/ClasspathCompareStrategy.java b/subprojects/core/src/main/java/org/gradle/internal/fingerprint/classpath/impl/ClasspathCompareStrategy.java index 96abe290abec2..38872724aa658 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/fingerprint/classpath/impl/ClasspathCompareStrategy.java +++ b/subprojects/core/src/main/java/org/gradle/internal/fingerprint/classpath/impl/ClasspathCompareStrategy.java @@ -18,7 +18,7 @@ import org.gradle.internal.change.Change; import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.FileChange; +import org.gradle.internal.change.DefaultFileChange; import org.gradle.internal.fingerprint.FileCollectionFingerprint; import org.gradle.internal.fingerprint.FileSystemLocationFingerprint; import org.gradle.internal.fingerprint.FingerprintCompareStrategy; @@ -137,18 +137,21 @@ void processChange() { void added() { if (includeAdded) { - changeConsumer.visitChange(FileChange.added(current.getKey(), propertyTitle, current.getValue().getType())); + DefaultFileChange added = DefaultFileChange.added(current.getKey(), propertyTitle, current.getValue().getType(), current.getValue().getNormalizedPath()); + changeConsumer.visitChange(added); } current = nextEntry(currentEntries); } void removed() { - changeConsumer.visitChange(FileChange.removed(previous.getKey(), propertyTitle, previous.getValue().getType())); + DefaultFileChange removed = DefaultFileChange.removed(previous.getKey(), propertyTitle, previous.getValue().getType(), previous.getValue().getNormalizedPath()); + changeConsumer.visitChange(removed); previous = nextEntry(previousEntries); } void modified() { - changeConsumer.visitChange(FileChange.modified(current.getKey(), propertyTitle, previous.getValue().getType(), current.getValue().getType())); + DefaultFileChange modified = DefaultFileChange.modified(current.getKey(), propertyTitle, previous.getValue().getType(), current.getValue().getType(), current.getValue().getNormalizedPath()); + changeConsumer.visitChange(modified); previous = nextEntry(previousEntries); current = nextEntry(currentEntries); } diff --git a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/BuildScopeServices.java b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/BuildScopeServices.java index 01cb4fa543dc8..e034710df03c1 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/BuildScopeServices.java +++ b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/BuildScopeServices.java @@ -152,6 +152,7 @@ import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.logging.BuildOperationLoggerFactory; import org.gradle.internal.operations.logging.DefaultBuildOperationLoggerFactory; +import org.gradle.internal.reflect.DirectInstantiator; import org.gradle.internal.reflect.Instantiator; import org.gradle.internal.resource.TextResourceLoader; import org.gradle.internal.service.CachingServiceLocator; @@ -258,14 +259,14 @@ protected ITaskFactory createITaskFactory(Instantiator instantiator, TaskClassIn protected ScriptCompilerFactory createScriptCompileFactory(ListenerManager listenerManager, FileCacheBackedScriptClassCompiler scriptCompiler, - CrossBuildInMemoryCachingScriptClassCache cache, - InstantiatorFactory instantiatorFactory) { + CrossBuildInMemoryCachingScriptClassCache cache) { ScriptExecutionListener scriptExecutionListener = listenerManager.getBroadcaster(ScriptExecutionListener.class); return new DefaultScriptCompilerFactory( new BuildScopeInMemoryCachingScriptClassCompiler(cache, scriptCompiler), new DefaultScriptRunnerFactory( scriptExecutionListener, - instantiatorFactory.inject() + // Should use `InstantiatorFactory` instead to pick up some validation, but this is currently somewhat expensive + DirectInstantiator.INSTANCE ) ); } diff --git a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/ExecutionGlobalServices.java b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/ExecutionGlobalServices.java index ca0e2b611aae9..a021cb3335e94 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/ExecutionGlobalServices.java +++ b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/ExecutionGlobalServices.java @@ -22,6 +22,7 @@ import org.gradle.api.internal.tasks.properties.InspectionSchemeFactory; import org.gradle.api.internal.tasks.properties.PropertyWalker; import org.gradle.api.internal.tasks.properties.TaskScheme; +import org.gradle.api.internal.tasks.properties.annotations.CacheableTaskTypeAnnotationHandler; import org.gradle.api.internal.tasks.properties.annotations.DestroysPropertyAnnotationHandler; import org.gradle.api.internal.tasks.properties.annotations.InputDirectoryPropertyAnnotationHandler; import org.gradle.api.internal.tasks.properties.annotations.InputFilePropertyAnnotationHandler; @@ -35,6 +36,8 @@ import org.gradle.api.internal.tasks.properties.annotations.OutputFilePropertyAnnotationHandler; import org.gradle.api.internal.tasks.properties.annotations.OutputFilesPropertyAnnotationHandler; import org.gradle.api.internal.tasks.properties.annotations.PropertyAnnotationHandler; +import org.gradle.api.internal.tasks.properties.annotations.TypeAnnotationHandler; +import org.gradle.api.model.ReplacedBy; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.CompileClasspath; import org.gradle.api.tasks.Console; @@ -55,17 +58,16 @@ import org.gradle.internal.instantiation.InstantiationScheme; import org.gradle.internal.instantiation.InstantiatorFactory; -import javax.inject.Inject; import java.util.List; public class ExecutionGlobalServices { - InspectionSchemeFactory createInspectionSchemeFactory(List handlers, CrossBuildInMemoryCacheFactory cacheFactory) { - return new InspectionSchemeFactory(handlers, cacheFactory); + InspectionSchemeFactory createInspectionSchemeFactory(List propertyHandlers, List typeHandlers, CrossBuildInMemoryCacheFactory cacheFactory) { + return new InspectionSchemeFactory(propertyHandlers, typeHandlers, cacheFactory); } TaskScheme createTaskScheme(InspectionSchemeFactory inspectionSchemeFactory, InstantiatorFactory instantiatorFactory) { InstantiationScheme instantiationScheme = instantiatorFactory.decorateScheme(); - InspectionScheme inspectionScheme = inspectionSchemeFactory.inspectionScheme(ImmutableSet.of(Input.class, InputFile.class, InputFiles.class, InputDirectory.class, OutputFile.class, OutputFiles.class, OutputDirectory.class, OutputDirectories.class, Classpath.class, CompileClasspath.class, Destroys.class, LocalState.class, Nested.class, Inject.class, Console.class, Internal.class, OptionValues.class)); + InspectionScheme inspectionScheme = inspectionSchemeFactory.inspectionScheme(ImmutableSet.of(Input.class, InputFile.class, InputFiles.class, InputDirectory.class, OutputFile.class, OutputFiles.class, OutputDirectory.class, OutputDirectories.class, Classpath.class, CompileClasspath.class, Destroys.class, LocalState.class, Nested.class, Console.class, ReplacedBy.class, Internal.class, OptionValues.class), instantiationScheme); return new TaskScheme(instantiationScheme, inspectionScheme); } @@ -77,8 +79,8 @@ TaskClassInfoStore createTaskClassInfoStore(CrossBuildInMemoryCacheFactory cache return new DefaultTaskClassInfoStore(cacheFactory); } - PropertyAnnotationHandler createInjectAnnotationHandler() { - return new NoOpPropertyAnnotationHandler(Inject.class); + TypeAnnotationHandler createCacheableTaskAnnotationHandler() { + return new CacheableTaskTypeAnnotationHandler(); } PropertyAnnotationHandler createConsoleAnnotationHandler() { @@ -89,6 +91,10 @@ PropertyAnnotationHandler createInternalAnnotationHandler() { return new NoOpPropertyAnnotationHandler(Internal.class); } + PropertyAnnotationHandler createReplacedByAnnotationHandler() { + return new NoOpPropertyAnnotationHandler(ReplacedBy.class); + } + PropertyAnnotationHandler createOptionValuesAnnotationHandler() { return new NoOpPropertyAnnotationHandler(OptionValues.class); } diff --git a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/ExecutionGradleServices.java b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/ExecutionGradleServices.java index f9cf1e9368006..7d834cce70413 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/ExecutionGradleServices.java +++ b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/ExecutionGradleServices.java @@ -31,31 +31,38 @@ import org.gradle.internal.concurrent.ExecutorFactory; import org.gradle.internal.concurrent.ParallelismConfigurationManager; import org.gradle.internal.event.ListenerManager; +import org.gradle.internal.execution.CachingResult; +import org.gradle.internal.execution.CurrentSnapshotResult; +import org.gradle.internal.execution.IncrementalChangesContext; +import org.gradle.internal.execution.IncrementalContext; import org.gradle.internal.execution.OutputChangeListener; import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.UpToDateResult; import org.gradle.internal.execution.WorkExecutor; import org.gradle.internal.execution.history.ExecutionHistoryCacheAccess; import org.gradle.internal.execution.history.ExecutionHistoryStore; import org.gradle.internal.execution.history.OutputFilesRepository; +import org.gradle.internal.execution.history.changes.ExecutionStateChangeDetector; import org.gradle.internal.execution.history.impl.DefaultExecutionHistoryStore; import org.gradle.internal.execution.history.impl.DefaultOutputFilesRepository; import org.gradle.internal.execution.impl.DefaultWorkExecutor; -import org.gradle.internal.execution.impl.steps.CacheStep; -import org.gradle.internal.execution.impl.steps.CachingContext; -import org.gradle.internal.execution.impl.steps.CancelExecutionStep; -import org.gradle.internal.execution.impl.steps.CatchExceptionStep; -import org.gradle.internal.execution.impl.steps.Context; -import org.gradle.internal.execution.impl.steps.CreateOutputsStep; -import org.gradle.internal.execution.impl.steps.CurrentSnapshotResult; -import org.gradle.internal.execution.impl.steps.ExecuteStep; -import org.gradle.internal.execution.impl.steps.PrepareCachingStep; -import org.gradle.internal.execution.impl.steps.SkipUpToDateStep; -import org.gradle.internal.execution.impl.steps.SnapshotOutputStep; -import org.gradle.internal.execution.impl.steps.StoreSnapshotsStep; -import org.gradle.internal.execution.impl.steps.TimeoutStep; -import org.gradle.internal.execution.impl.steps.UpToDateResult; +import org.gradle.internal.execution.steps.BroadcastChangingOutputsStep; +import org.gradle.internal.execution.steps.CacheStep; +import org.gradle.internal.execution.steps.CancelExecutionStep; +import org.gradle.internal.execution.steps.CatchExceptionStep; +import org.gradle.internal.execution.steps.CreateOutputsStep; +import org.gradle.internal.execution.steps.ExecuteStep; +import org.gradle.internal.execution.steps.RecordOutputsStep; +import org.gradle.internal.execution.steps.ResolveCachingStateStep; +import org.gradle.internal.execution.steps.ResolveChangesStep; +import org.gradle.internal.execution.steps.SkipUpToDateStep; +import org.gradle.internal.execution.steps.SnapshotOutputsStep; +import org.gradle.internal.execution.steps.StoreSnapshotsStep; +import org.gradle.internal.execution.steps.TimeoutStep; +import org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep; import org.gradle.internal.execution.timeout.TimeoutHandler; import org.gradle.internal.resources.ResourceLockCoordinationService; +import org.gradle.internal.scan.config.BuildScanPluginApplied; import org.gradle.internal.scopeids.id.BuildInvocationScopeId; import org.gradle.internal.work.WorkerLeaseService; import org.gradle.util.GradleVersion; @@ -109,26 +116,36 @@ OutputChangeListener createOutputChangeListener(ListenerManager listenerManager) return listenerManager.getBroadcaster(OutputChangeListener.class); } - public WorkExecutor createWorkExecutor( - BuildCacheController buildCacheController, + public WorkExecutor createWorkExecutor( BuildCacheCommandFactory buildCacheCommandFactory, - BuildInvocationScopeId buildInvocationScopeId, + BuildCacheController buildCacheController, + BuildScanPluginApplied buildScanPlugin, BuildCancellationToken cancellationToken, + BuildInvocationScopeId buildInvocationScopeId, + ExecutionStateChangeDetector changeDetector, OutputChangeListener outputChangeListener, OutputFilesRepository outputFilesRepository, TimeoutHandler timeoutHandler ) { - return new DefaultWorkExecutor( - new SkipUpToDateStep( - new StoreSnapshotsStep(outputFilesRepository, - new PrepareCachingStep( - new CacheStep(buildCacheController, outputChangeListener, buildCacheCommandFactory, - new SnapshotOutputStep(buildInvocationScopeId.getId(), - new CreateOutputsStep( - new CatchExceptionStep( - new TimeoutStep(timeoutHandler, - new CancelExecutionStep(cancellationToken, - new ExecuteStep(outputChangeListener) + return new DefaultWorkExecutor( + new ResolveCachingStateStep(buildCacheController, buildScanPlugin.isBuildScanPluginApplied(), + new MarkSnapshottingInputsFinishedStep( + new ResolveChangesStep(changeDetector, + new SkipUpToDateStep( + new RecordOutputsStep(outputFilesRepository, + new StoreSnapshotsStep( + new BroadcastChangingOutputsStep(outputChangeListener, + new CacheStep(buildCacheController, buildCacheCommandFactory, + new SnapshotOutputsStep(buildInvocationScopeId.getId(), + new CreateOutputsStep( + new CatchExceptionStep( + new TimeoutStep(timeoutHandler, + new CancelExecutionStep(cancellationToken, + new ExecuteStep() + ) + ) + ) + ) ) ) ) diff --git a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GlobalScopeServices.java b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GlobalScopeServices.java index 8da9413547d2f..d8947483c9a7b 100755 --- a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GlobalScopeServices.java +++ b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GlobalScopeServices.java @@ -70,6 +70,8 @@ import org.gradle.internal.concurrent.ParallelismConfigurationManager; import org.gradle.internal.environment.GradleBuildEnvironment; import org.gradle.internal.event.ListenerManager; +import org.gradle.internal.execution.history.changes.DefaultExecutionStateChangeDetector; +import org.gradle.internal.execution.history.changes.ExecutionStateChangeDetector; import org.gradle.internal.filewatch.DefaultFileWatcherFactory; import org.gradle.internal.filewatch.FileWatcherFactory; import org.gradle.internal.hash.DefaultStreamHasher; @@ -355,4 +357,8 @@ StreamHasher createStreamHasher() { Clock createClock() { return Time.clock(); } + + ExecutionStateChangeDetector createExecutionStateChangeDetector() { + return new DefaultExecutionStateChangeDetector(); + } } diff --git a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GradleUserHomeScopeServiceRegistry.java b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GradleUserHomeScopeServiceRegistry.java index d37c4fcd001bc..0bc8ba72fd058 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GradleUserHomeScopeServiceRegistry.java +++ b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GradleUserHomeScopeServiceRegistry.java @@ -16,7 +16,7 @@ package org.gradle.internal.service.scopes; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.service.ServiceRegistry; import java.io.File; diff --git a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GradleUserHomeScopeServices.java b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GradleUserHomeScopeServices.java index 96912aab2f4fd..274183ab01026 100644 --- a/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GradleUserHomeScopeServices.java +++ b/subprojects/core/src/main/java/org/gradle/internal/service/scopes/GradleUserHomeScopeServices.java @@ -35,7 +35,6 @@ import org.gradle.api.internal.initialization.loadercache.ClassLoaderCache; import org.gradle.api.internal.initialization.loadercache.DefaultClassLoaderCache; import org.gradle.api.internal.initialization.loadercache.DefaultClasspathHasher; -import org.gradle.api.internal.model.NamedObjectInstantiator; import org.gradle.cache.CacheRepository; import org.gradle.cache.PersistentIndexedCache; import org.gradle.cache.PersistentIndexedCacheParameters; @@ -149,7 +148,7 @@ CrossBuildInMemoryCachingScriptClassCache createCachingScriptCompiler(CrossBuild } DefaultValueSnapshotter createValueSnapshotter(ClassLoaderHierarchyHasher classLoaderHierarchyHasher) { - return new DefaultValueSnapshotter(classLoaderHierarchyHasher, NamedObjectInstantiator.INSTANCE); + return new DefaultValueSnapshotter(classLoaderHierarchyHasher); } ClassLoaderHierarchyHasher createClassLoaderHierarchyHasher(ClassLoaderRegistry registry, ClassLoaderHasher classLoaderHasher) { diff --git a/subprojects/core/src/main/java/org/gradle/plugin/management/internal/autoapply/AutoAppliedBuildScanPlugin.java b/subprojects/core/src/main/java/org/gradle/plugin/management/internal/autoapply/AutoAppliedBuildScanPlugin.java index b5868ce31847e..b8055cbbfe8fc 100644 --- a/subprojects/core/src/main/java/org/gradle/plugin/management/internal/autoapply/AutoAppliedBuildScanPlugin.java +++ b/subprojects/core/src/main/java/org/gradle/plugin/management/internal/autoapply/AutoAppliedBuildScanPlugin.java @@ -31,7 +31,7 @@ public final class AutoAppliedBuildScanPlugin { public static final PluginId ID = new DefaultPluginId("com.gradle.build-scan"); public static final String GROUP = "com.gradle"; public static final String NAME = "build-scan-plugin"; - public static final String VERSION = "2.1"; + public static final String VERSION = "2.2.1"; /** * Adds the {@code build-scan} plugin spec to the given {@link PluginDependenciesSpec} and returns the diff --git a/subprojects/core/src/main/java/org/gradle/process/internal/JavaExecHandleBuilder.java b/subprojects/core/src/main/java/org/gradle/process/internal/JavaExecHandleBuilder.java index 27801c337567b..369cd193b30d4 100644 --- a/subprojects/core/src/main/java/org/gradle/process/internal/JavaExecHandleBuilder.java +++ b/subprojects/core/src/main/java/org/gradle/process/internal/JavaExecHandleBuilder.java @@ -207,12 +207,14 @@ public List getArgumentProviders() { } public JavaExecHandleBuilder setClasspath(FileCollection classpath) { - doGetClasspath().setFrom(classpath); + ConfigurableFileCollection newClasspath = fileCollectionFactory.configurableFiles("classpath"); + newClasspath.setFrom(classpath); + this.classpath = newClasspath; return this; } public JavaExecHandleBuilder classpath(Object... paths) { - doGetClasspath().setFrom(paths); + doGetClasspath().from(paths); return this; } diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy index da363bd969075..51fab1c368f39 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/DefaultTaskTest.groovy @@ -23,13 +23,14 @@ import org.gradle.api.InvalidUserDataException import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.internal.project.taskfactory.TaskIdentity -import org.gradle.api.internal.tasks.ContextAwareTaskAction +import org.gradle.api.internal.tasks.InputChangesAwareTaskAction import org.gradle.api.logging.Logger import org.gradle.api.tasks.AbstractTaskTest import org.gradle.api.tasks.TaskExecutionException import org.gradle.api.tasks.TaskInstantiationException import org.gradle.internal.Actions import org.gradle.internal.event.ListenerManager +import org.gradle.internal.logging.slf4j.ContextAwareTaskLogger import spock.lang.Issue import java.util.concurrent.Callable @@ -495,7 +496,7 @@ class DefaultTaskTest extends AbstractTaskTest { def "describable actions are not renamed"() { setup: - def namedAction = Mock(ContextAwareTaskAction) + def namedAction = Mock(InputChangesAwareTaskAction) namedAction.displayName >> "I have a name" when: @@ -526,7 +527,8 @@ class DefaultTaskTest extends AbstractTaskTest { def "can replace task logger"() { expect: - task.logger == AbstractTask.BUILD_LOGGER + task.logger instanceof ContextAwareTaskLogger + task.logger.delegate == AbstractTask.BUILD_LOGGER when: def logger = Mock(Logger) diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/TargetJvmVersionRulesTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/TargetJvmVersionRulesTest.groovy new file mode 100644 index 0000000000000..96b90b291c899 --- /dev/null +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/artifacts/TargetJvmVersionRulesTest.groovy @@ -0,0 +1,93 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.artifacts + +import org.gradle.api.attributes.AttributesSchema +import org.gradle.api.attributes.java.TargetJvmVersion +import org.gradle.api.internal.attributes.CompatibilityCheckResult +import org.gradle.api.internal.attributes.CompatibilityRule +import org.gradle.api.internal.attributes.DefaultAttributesSchema +import org.gradle.api.internal.attributes.DisambiguationRule +import org.gradle.api.internal.attributes.MultipleCandidatesResult +import org.gradle.internal.component.model.ComponentAttributeMatcher +import org.gradle.util.SnapshotTestUtil +import org.gradle.util.TestUtil +import spock.lang.Specification +import spock.lang.Unroll + +class TargetJvmVersionRulesTest extends Specification { + private CompatibilityRule compatibilityRules + private DisambiguationRule disambiguationRules + + def setup() { + AttributesSchema schema = new DefaultAttributesSchema(Stub(ComponentAttributeMatcher), TestUtil.instantiatorFactory(), SnapshotTestUtil.valueSnapshotter()) + JavaEcosystemSupport.configureSchema(schema, TestUtil.objectFactory()) + compatibilityRules = schema.compatibilityRules(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE) + disambiguationRules = schema.disambiguationRules(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE) + } + + @Unroll("compatibility consumer=#consumer producer=#producer compatible=#compatible") + def "check compatibility rules"() { + CompatibilityCheckResult details = Mock(CompatibilityCheckResult) + + when: + compatibilityRules.execute(details) + + then: + 1 * details.getConsumerValue() >> consumer + 1 * details.getProducerValue() >> producer + + if (compatible) { + 1 * details.compatible() + } else { + 1 * details.incompatible() + } + + where: + consumer | producer | compatible + 8 | 6 | true + 8 | 7 | true + 8 | 8 | true + 8 | 9 | false + 8 | 10 | false + 8 | 11 | false + } + + @Unroll("disamgiguates when consumer=#consumer and candidates=#candidates chooses=#expected") + def "check disambiguation rules"() { + MultipleCandidatesResult details = Mock() + + when: + disambiguationRules.execute(details) + + then: + 1 * details.getCandidateValues() >> candidates + 1 * details.closestMatch(expected) + 1 * details.hasResult() + 0 * details._ + + where: + consumer | candidates | expected + 6 | [6] | 6 + 7 | [6, 7] | 7 + 8 | [6, 7] | 7 + 9 | [6, 7, 9] | 9 + 10 | [6, 7, 9] | 9 + 11 | [6, 7, 9] | 9 + + } +} diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultProjectLayoutTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultProjectLayoutTest.groovy index 8ff3f6456f14d..ebb38ee2f4c13 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultProjectLayoutTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/file/DefaultProjectLayoutTest.groovy @@ -25,6 +25,8 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider import org.junit.Rule import spock.lang.Specification +import static org.gradle.util.Matchers.strictlyEquals + class DefaultProjectLayoutTest extends Specification { @Rule TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider() @@ -560,6 +562,26 @@ class DefaultProjectLayoutTest extends Specification { file2.getAsFile() == projectDir.file("build/b") } + def "directories are equal when their paths are equal"() { + expect: + def dir = layout.projectDirectory.dir("child") + strictlyEquals(dir, layout.projectDirectory.dir("child")) + + dir != layout.projectDirectory.dir("other") + dir != layout.projectDirectory.dir("child/child2") + dir != layout.projectDirectory.file("child") + } + + def "regular files are equal when their paths are equal"() { + expect: + def file = layout.projectDirectory.file("child") + strictlyEquals(file, layout.projectDirectory.file("child")) + + file != layout.projectDirectory.file("other") + file != layout.projectDirectory.file("child/child2") + file != layout.projectDirectory.dir("child") + } + def "can wrap File provider"() { def fileProvider = Stub(ProviderInternal) def file1 = projectDir.file("file1") diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy index fe5f0ef5fcb3a..64093b53b4d14 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/initialization/DefaultScriptHandlerTest.groovy @@ -22,6 +22,7 @@ import org.gradle.api.artifacts.dsl.RepositoryHandler import org.gradle.api.attributes.AttributesSchema import org.gradle.api.attributes.Bundling import org.gradle.api.attributes.Usage +import org.gradle.api.attributes.java.TargetJvmVersion import org.gradle.api.internal.artifacts.DependencyResolutionServices import org.gradle.api.internal.attributes.AttributeContainerInternal import org.gradle.groovy.scripts.ScriptSource @@ -54,9 +55,10 @@ class DefaultScriptHandlerTest extends Specification { then: 1 * depMgmtServices.configurationContainer >> configurationContainer 1 * configurationContainer.create('classpath') >> configuration - 2 * configuration.attributes >> attributes + 1 * configuration.attributes >> attributes 1 * attributes.attribute(Usage.USAGE_ATTRIBUTE, _ as Usage) 1 * attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, _ as Bundling) + 1 * attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, _) 0 * configurationContainer._ 0 * depMgmtServices._ } @@ -69,9 +71,10 @@ class DefaultScriptHandlerTest extends Specification { then: 1 * depMgmtServices.configurationContainer >> configurationContainer 1 * configurationContainer.create('classpath') >> configuration - 2 * configuration.attributes >> attributes + 1 * configuration.attributes >> attributes 1 * attributes.attribute(Usage.USAGE_ATTRIBUTE, _ as Usage) 1 * attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, _ as Bundling) + 1 * attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, _) 1 * depMgmtServices.dependencyHandler >> dependencyHandler 0 * configurationContainer._ 0 * depMgmtServices._ @@ -102,8 +105,9 @@ class DefaultScriptHandlerTest extends Specification { and: 1 * depMgmtServices.configurationContainer >> configurationContainer 1 * configurationContainer.create('classpath') >> configuration - 2 * configuration.attributes >> attributes + 1 * configuration.attributes >> attributes 1 * attributes.attribute(Usage.USAGE_ATTRIBUTE, _ as Usage) + 1 * attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, _) 1 * attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, _ as Bundling) 1 * classpathResolver.resolveClassPath(configuration) >> classpath } @@ -132,9 +136,10 @@ class DefaultScriptHandlerTest extends Specification { 1 * depMgmtServices.dependencyHandler >> dependencyHandler 1 * depMgmtServices.configurationContainer >> configurationContainer 1 * configurationContainer.create('classpath') >> configuration - 2 * configuration.attributes >> attributes + 1 * configuration.attributes >> attributes 1 * attributes.attribute(Usage.USAGE_ATTRIBUTE, _ as Usage) 1 * attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, _ as Bundling) + 1 * attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, _) 1 * dependencyHandler.add('config', 'dep') } } diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.groovy index c95c2e44ee81d..15f8d6a08c0ca 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTaskFactoryTest.groovy @@ -38,6 +38,7 @@ import org.gradle.internal.service.ServiceRegistryBuilder import org.gradle.internal.service.scopes.ExecutionGlobalServices import org.gradle.test.fixtures.AbstractProjectBuilderSpec import org.gradle.test.fixtures.file.TestFile +import org.gradle.work.InputChanges import spock.lang.Unroll import java.util.concurrent.Callable @@ -48,6 +49,7 @@ import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTa import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.BrokenTaskWithInputDir import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.BrokenTaskWithInputFiles import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.NamedBean +import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskUsingInputChanges import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithBooleanInput import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithBridgeMethod import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithDestroyable @@ -59,8 +61,10 @@ import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTa import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithInputFiles import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithJavaBeanCornerCaseProperties import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithLocalState +import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithMixedMultipleIncrementalActions import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithMultiParamAction import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithMultipleIncrementalActions +import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithMultipleInputChangesActions import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithMultipleMethods import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithMultipleProperties import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithNestedBean @@ -79,7 +83,10 @@ import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTa import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithOutputFile import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithOutputFiles import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithOverloadedActions +import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithOverloadedIncrementalAndInputChangesActions +import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithOverloadedInputChangesActions import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithOverriddenIncrementalAction +import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithOverriddenInputChangesAction import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithOverriddenMethod import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithProtectedMethod import static org.gradle.api.internal.project.taskfactory.AnnotationProcessingTasks.TaskWithSingleParamAction @@ -91,7 +98,7 @@ class AnnotationProcessingTaskFactoryTest extends AbstractProjectBuilderSpec { private ITaskFactory delegate def services = ServiceRegistryBuilder.builder().provider(new ExecutionGlobalServices()).build() def taskClassInfoStore = new DefaultTaskClassInfoStore(new TestCrossBuildInMemoryCacheFactory()) - def propertyWalker = new DefaultPropertyWalker(new DefaultTypeMetadataStore(services.getAll(PropertyAnnotationHandler), [] as Set, new TestCrossBuildInMemoryCacheFactory())) + def propertyWalker = new DefaultPropertyWalker(new DefaultTypeMetadataStore(services.getAll(PropertyAnnotationHandler), [] as Set, [] as List, new TestCrossBuildInMemoryCacheFactory())) @SuppressWarnings("GroovyUnusedDeclaration") private String inputValue = "value" @@ -158,6 +165,19 @@ class AnnotationProcessingTaskFactoryTest extends AbstractProjectBuilderSpec { 0 * _ } + def createsContextualActionForInputChangesTaskAction() { + given: + def action = Mock(Action) + def task = expectTaskCreated(TaskUsingInputChanges, action) + + when: + execute(task) + + then: + 1 * action.execute(_ as InputChanges) + 0 * _ + } + def createsContextualActionForOverriddenIncrementalTaskAction() { given: def action = Mock(Action) @@ -172,6 +192,20 @@ class AnnotationProcessingTaskFactoryTest extends AbstractProjectBuilderSpec { 0 * _ } + def createsContextualActionForOverriddenInputChangesTaskAction() { + given: + def action = Mock(Action) + def superAction = Mock(Action) + def task = expectTaskCreated(TaskWithOverriddenInputChangesAction, action, superAction) + + when: + execute(task) + + then: + 1 * action.execute(_ as InputChanges) + 0 * _ + } + def cachesClassMetaInfo() { given: def taskInfo1 = taskClassInfoStore.getTaskClassInfo(TaskWithInputFile) @@ -191,12 +225,16 @@ class AnnotationProcessingTaskFactoryTest extends AbstractProjectBuilderSpec { e.message == failureMessage where: - type | failureMessage - TaskWithMultipleIncrementalActions | "Cannot have multiple @TaskAction methods accepting an IncrementalTaskInputs parameter." - TaskWithStaticMethod | "Cannot use @TaskAction annotation on static method TaskWithStaticMethod.doStuff()." - TaskWithMultiParamAction | "Cannot use @TaskAction annotation on method TaskWithMultiParamAction.doStuff() as this method takes multiple parameters." - TaskWithSingleParamAction | "Cannot use @TaskAction annotation on method TaskWithSingleParamAction.doStuff() because int is not a valid parameter to an action method." - TaskWithOverloadedActions | "Cannot use @TaskAction annotation on multiple overloads of method TaskWithOverloadedActions.doStuff()" + type | failureMessage + TaskWithMultipleIncrementalActions | "Cannot have multiple @TaskAction methods accepting an InputChanges or IncrementalTaskInputs parameter." + TaskWithStaticMethod | "Cannot use @TaskAction annotation on static method TaskWithStaticMethod.doStuff()." + TaskWithMultiParamAction | "Cannot use @TaskAction annotation on method TaskWithMultiParamAction.doStuff() as this method takes multiple parameters." + TaskWithSingleParamAction | "Cannot use @TaskAction annotation on method TaskWithSingleParamAction.doStuff() because int is not a valid parameter to an action method." + TaskWithOverloadedActions | "Cannot use @TaskAction annotation on multiple overloads of method TaskWithOverloadedActions.doStuff()" + TaskWithOverloadedInputChangesActions | "Cannot use @TaskAction annotation on multiple overloads of method TaskWithOverloadedInputChangesActions.doStuff()" + TaskWithOverloadedIncrementalAndInputChangesActions | "Cannot use @TaskAction annotation on multiple overloads of method TaskWithOverloadedIncrementalAndInputChangesActions.doStuff()" + TaskWithMultipleInputChangesActions | "Cannot have multiple @TaskAction methods accepting an InputChanges or IncrementalTaskInputs parameter." + TaskWithMixedMultipleIncrementalActions | "Cannot have multiple @TaskAction methods accepting an InputChanges or IncrementalTaskInputs parameter." } @Unroll diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTasks.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTasks.java index 1b7a1b9d20291..ec56e4fa803ae 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTasks.java +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/project/taskfactory/AnnotationProcessingTasks.java @@ -34,6 +34,7 @@ import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.incremental.IncrementalTaskInputs; +import org.gradle.work.InputChanges; import java.io.File; import java.util.List; @@ -146,6 +147,53 @@ public void doStuff(IncrementalTaskInputs changes) { } } + public static class TaskWithOverloadedActions extends DefaultTask { + @TaskAction + public void doStuff() {} + + @TaskAction + public void doStuff(IncrementalTaskInputs changes) {} + } + + public static class TaskUsingInputChanges extends DefaultTask { + private final Action action; + + public TaskUsingInputChanges(Action action) { + this.action = action; + } + + @TaskAction + public void doStuff(InputChanges changes) { + action.execute(changes); + } + } + + public static class TaskWithOverriddenInputChangesAction extends TaskUsingInputChanges { + private final Action action; + + public TaskWithOverriddenInputChangesAction(Action action, Action superAction) { + super(superAction); + this.action = action; + } + + @Override + @TaskAction + public void doStuff(InputChanges changes) { + action.execute(changes); + } + } + + public static class TaskWithMultipleInputChangesActions extends DefaultTask { + + @TaskAction + public void doStuff(InputChanges changes) { + } + + @TaskAction + public void doStuff2(InputChanges changes) { + } + } + public static class TaskWithMultipleIncrementalActions extends DefaultTask { @TaskAction @@ -157,12 +205,31 @@ public void doStuff2(IncrementalTaskInputs changes) { } } - public static class TaskWithOverloadedActions extends DefaultTask { + public static class TaskWithMixedMultipleIncrementalActions extends DefaultTask { + + @TaskAction + public void doStuff(IncrementalTaskInputs changes) { + } + + @TaskAction + public void doStuff2(InputChanges changes) { + } + } + + public static class TaskWithOverloadedInputChangesActions extends DefaultTask { @TaskAction public void doStuff() {} + @TaskAction + public void doStuff(InputChanges changes) {} + } + + public static class TaskWithOverloadedIncrementalAndInputChangesActions extends DefaultTask { @TaskAction public void doStuff(IncrementalTaskInputs changes) {} + + @TaskAction + public void doStuff(InputChanges changes) {} } public static class TaskWithSingleParamAction extends DefaultTask { diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy index c9a0f9160fef7..541326675dcdb 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskInputsTest.groovy @@ -61,7 +61,7 @@ class DefaultTaskInputsTest extends Specification { getDestroyables() >> Stub(TaskDestroyablesInternal) getLocalState() >> Stub(TaskLocalStateInternal) } - def walker = new DefaultPropertyWalker(new DefaultTypeMetadataStore([], [] as Set, new TestCrossBuildInMemoryCacheFactory())) + def walker = new DefaultPropertyWalker(new DefaultTypeMetadataStore([], [] as Set, [] as List, new TestCrossBuildInMemoryCacheFactory())) private final DefaultTaskInputs inputs = new DefaultTaskInputs(task, taskStatusNagger, walker, fileCollectionFactory) def "default values"() { @@ -298,7 +298,7 @@ class DefaultTaskInputsTest extends Specification { when: inputs.visitRegisteredProperties(new PropertyVisitor.Adapter() { @Override - void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { names += propertyName } }) @@ -327,7 +327,7 @@ class DefaultTaskInputsTest extends Specification { def inputFiles = [:] TaskPropertyUtils.visitProperties(walker, task, new PropertyVisitor.Adapter() { @Override - void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { inputFiles[propertyName] = value.call() } }) diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy index 8540edec2ec3e..aa05f26a26e3c 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/DefaultTaskOutputsTest.groovy @@ -63,7 +63,7 @@ class DefaultTaskOutputsTest extends Specification { getLocalState() >> Stub(TaskLocalStateInternal) } - private final DefaultTaskOutputs outputs = new DefaultTaskOutputs(task, taskStatusNagger, new DefaultPropertyWalker(new DefaultTypeMetadataStore([], [] as Set, new TestCrossBuildInMemoryCacheFactory())), fileCollectionFactory) + def outputs = new DefaultTaskOutputs(task, taskStatusNagger, new DefaultPropertyWalker(new DefaultTypeMetadataStore([], [] as Set, [] as List, new TestCrossBuildInMemoryCacheFactory())), fileCollectionFactory) void hasNoOutputsByDefault() { setup: diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/SnapshotTaskInputsBuildOperationResultTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/SnapshotTaskInputsBuildOperationResultTest.groovy new file mode 100644 index 0000000000000..bd6730dc3908a --- /dev/null +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/SnapshotTaskInputsBuildOperationResultTest.groovy @@ -0,0 +1,68 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSortedMap +import com.google.common.collect.ImmutableSortedSet +import org.gradle.internal.execution.caching.CachingInputs +import org.gradle.internal.execution.caching.CachingState +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint +import org.gradle.internal.hash.HashCode +import org.gradle.internal.snapshot.impl.ImplementationSnapshot +import spock.lang.Specification + +class SnapshotTaskInputsBuildOperationResultTest extends Specification { + + def "adapts key to result interface"() { + given: + def inputs = Mock(CachingInputs) + def cachingState = Mock(CachingState) { + getInputs() >> Optional.of(inputs) + } + def adapter = new SnapshotTaskInputsBuildOperationResult(cachingState) + + when: + inputs.inputValueFingerprints >> ImmutableSortedMap.copyOf(b: HashCode.fromInt(0x000000bb), a: HashCode.fromInt(0x000000aa)) + inputs.inputFileFingerprints >> ImmutableSortedMap.copyOf(c: { getHash: { HashCode.fromInt(0x000000cc) } } as CurrentFileCollectionFingerprint) + + then: + adapter.inputValueHashesBytes.collectEntries { [(it.key):HashCode.fromBytes(it.value).toString()] } == [a: "000000aa", b: "000000bb"] + + when: + inputs.nonCacheableInputProperties >> ImmutableSortedSet.of("bean", "someOtherBean") + then: + adapter.inputPropertiesLoadedByUnknownClassLoader == ["bean", "someOtherBean"] as SortedSet + + when: + inputs.implementation >> ImplementationSnapshot.of("org.gradle.TaskType", HashCode.fromInt(0x000000cc)) + then: + HashCode.fromBytes(adapter.classLoaderHashBytes).toString() == "000000cc" + + when: + inputs.additionalImplementations >> ImmutableList.copyOf([ImplementationSnapshot.of("foo", HashCode.fromInt(0x000000ee)), ImplementationSnapshot.of("bar", HashCode.fromInt(0x000000dd))]) + then: + adapter.actionClassLoaderHashesBytes.collect{ HashCode.fromBytes(it).toString() } == ["000000ee", "000000dd"] + adapter.actionClassNames == ["foo", "bar"] + + when: + inputs.outputProperties >> ImmutableSortedSet.copyOf(["2", "1"]) + then: + adapter.outputPropertyNames == ["1", "2"] + } + +} diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy index dd1f134167d2f..0c150cdd887f8 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/TaskStateInternalTest.groovy @@ -39,7 +39,6 @@ class TaskStateInternalTest { assertFalse(state.getSkipped()) assertThat(state.getSkipMessage(), nullValue()) assertFalse(state.upToDate) - assertFalse(state.taskOutputCaching.enabled) assertTrue(state.actionable) } diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/CatchExceptionTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/CatchExceptionTaskExecuterTest.groovy index fbd144208a5b3..e27c5f24d48a9 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/CatchExceptionTaskExecuterTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/CatchExceptionTaskExecuterTest.groovy @@ -39,7 +39,7 @@ class CatchExceptionTaskExecuterTest extends Specification { then: 1 * delegate.execute(task, state, context) >> { state.setOutcome(TaskExecutionOutcome.EXECUTED) - return TaskExecuterResult.NO_REUSED_OUTPUT + return TaskExecuterResult.WITHOUT_OUTPUTS } 0 * _ state.outcome == TaskExecutionOutcome.EXECUTED diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/DefaultTaskCacheabilityResolverTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/DefaultTaskCacheabilityResolverTest.groovy new file mode 100644 index 0000000000000..4714139c1645a --- /dev/null +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/DefaultTaskCacheabilityResolverTest.groovy @@ -0,0 +1,172 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.execution + +import com.google.common.collect.ImmutableSortedSet +import org.gradle.api.GradleException +import org.gradle.api.internal.OverlappingOutputs +import org.gradle.api.internal.TaskInternal +import org.gradle.api.internal.tasks.properties.CacheableOutputFilePropertySpec +import org.gradle.api.internal.tasks.properties.OutputFilePropertySpec +import org.gradle.api.specs.Spec +import org.gradle.internal.execution.caching.CachingDisabledReason +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory +import org.gradle.internal.file.RelativeFilePathResolver +import spock.lang.Specification + +import javax.annotation.Nullable + +class DefaultTaskCacheabilityResolverTest extends Specification { + def task = Stub(TaskInternal) + def cacheableOutputProperty = Mock(CacheableOutputFilePropertySpec) + def relativeFilePathResolver = Mock(RelativeFilePathResolver) + def resolver = new DefaultTaskCacheabilityResolver(relativeFilePathResolver) + + def "report no reason if the task is cacheable"() { + expect: + determineNoCacheReason( + [cacheableOutputProperty], + [spec({ true })], + ) == null + } + + def "caching is disabled with no outputs"() { + when: + def reason = determineNoCacheReason( + [], + [spec({ true })], + ) + + then: + reason.category == CachingDisabledReasonCategory.NO_OUTPUTS_DECLARED + reason.message == "No outputs declared" + } + + def "no cacheIf() means no caching"() { + when: + def reason = determineNoCacheReason( + [cacheableOutputProperty] + ) + + then: + reason.category == CachingDisabledReasonCategory.NOT_CACHEABLE + reason.message == "Caching has not been enabled for the task" + } + + def "can turn caching off via cacheIf()"() { + when: + def reason = determineNoCacheReason( + [cacheableOutputProperty], + [spec({ false }, "Cacheable test")] + ) + + then: + reason.category == CachingDisabledReasonCategory.ENABLE_CONDITION_NOT_SATISFIED + reason.message == "'Cacheable test' not satisfied" + } + + def "error message contains which cacheIf spec failed to evaluate"() { + when: + determineNoCacheReason( + [cacheableOutputProperty], + [spec({ throw new RuntimeException() }, "Exception is thrown")], + ) + + then: + def ex = thrown GradleException + ex.message == "Could not evaluate spec for 'Exception is thrown'." + } + + def "can turn caching off via doNotCacheIf()"() { + when: + def reason = determineNoCacheReason( + [cacheableOutputProperty], + [spec({ true })], + [spec({ true }, "Uncacheable test")] + ) + + then: + reason.category == CachingDisabledReasonCategory.DISABLE_CONDITION_SATISFIED + reason.message == "'Uncacheable test' satisfied" + } + + def "error message contains which doNotCacheIf spec failed to evaluate"() { + when: + determineNoCacheReason( + [cacheableOutputProperty], + [spec({ true })], + [spec({ "throw new RuntimeException()" }, "Exception is thrown")] + ) + + then: + def ex = thrown GradleException + ex.message == "Could not evaluate spec for 'Exception is thrown'." + } + + def "caching is disabled for non-cacheable file outputs is reported"() { + when: + def reason = determineNoCacheReason( + [Stub(OutputFilePropertySpec) { + getPropertyName() >> "non-cacheable property" + }], + [spec({ true })] + ) + + then: + reason.category == CachingDisabledReasonCategory.NON_CACHEABLE_OUTPUT + reason.message == "Output property 'non-cacheable property' contains a file tree" + } + + def "caching is disabled when cache key is invalid because of overlapping outputs"() { + def overlappingOutputs = new OverlappingOutputs("someProperty", "path/to/outputFile") + + when: + def reason = determineNoCacheReason( + [cacheableOutputProperty], + [spec({ true })], + [], + overlappingOutputs + ) + + then: + reason.category == CachingDisabledReasonCategory.OVERLAPPING_OUTPUTS + reason.message == "Gradle does not know how file 'relative/path' was created (output property 'someProperty'). Task output caching requires exclusive access to output paths to guarantee correctness." + + 1 * relativeFilePathResolver.resolveAsRelativePath(overlappingOutputs.overlappedFilePath) >> "relative/path" + } + + static def spec(Spec spec, String description = "test cacheIf()") { + new SelfDescribingSpec(spec, description) + } + + @Nullable + CachingDisabledReason determineNoCacheReason( + Collection outputFileProperties, + Collection> cacheIfSpecs = [], + Collection> doNotCacheIfSpecs = [], + @Nullable OverlappingOutputs overlappingOutputs = null + ) { + resolver.shouldDisableCaching( + !outputFileProperties.isEmpty(), + ImmutableSortedSet.copyOf(outputFileProperties), + task, + cacheIfSpecs, + doNotCacheIfSpecs, + overlappingOutputs + ).orElse(null) + } +} diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/EventFiringTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/EventFiringTaskExecuterTest.groovy index cdc38955d57a3..976408a7538d4 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/EventFiringTaskExecuterTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/EventFiringTaskExecuterTest.groovy @@ -48,7 +48,7 @@ class EventFiringTaskExecuterTest extends Specification { 1 * taskExecutionListener.beforeExecute(task) then: - 1 * delegate.execute(task, state, executionContext) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * delegate.execute(task, state, executionContext) >> TaskExecuterResult.WITHOUT_OUTPUTS then: 1 * taskExecutionListener.afterExecute(task, state) @@ -95,7 +95,7 @@ class EventFiringTaskExecuterTest extends Specification { then: 1 * delegate.execute(task, state, executionContext) >> { state.setOutcome(failure) - return TaskExecuterResult.NO_REUSED_OUTPUT + return TaskExecuterResult.WITHOUT_OUTPUTS } then: @@ -121,7 +121,7 @@ class EventFiringTaskExecuterTest extends Specification { 1 * taskExecutionListener.beforeExecute(task) then: - 1 * delegate.execute(task, state, executionContext) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * delegate.execute(task, state, executionContext) >> TaskExecuterResult.WITHOUT_OUTPUTS then: 1 * taskExecutionListener.afterExecute(task, state) >> { @@ -152,7 +152,7 @@ class EventFiringTaskExecuterTest extends Specification { then: 1 * delegate.execute(task, state, executionContext) >> { state.setOutcome(failure) - return TaskExecuterResult.NO_REUSED_OUTPUT + return TaskExecuterResult.WITHOUT_OUTPUTS } then: diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.groovy index 76c674aa67d8d..d7b1451525f17 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ExecuteActionsTaskExecuterTest.groovy @@ -19,9 +19,9 @@ import com.google.common.collect.ImmutableSortedMap import com.google.common.collect.ImmutableSortedSet import org.gradle.api.execution.TaskActionListener import org.gradle.api.internal.TaskInternal -import org.gradle.api.internal.cache.StringInterner +import org.gradle.api.internal.changedetection.TaskExecutionMode import org.gradle.api.internal.project.ProjectInternal -import org.gradle.api.internal.tasks.ContextAwareTaskAction +import org.gradle.api.internal.tasks.InputChangesAwareTaskAction import org.gradle.api.internal.tasks.TaskExecutionContext import org.gradle.api.internal.tasks.TaskExecutionOutcome import org.gradle.api.internal.tasks.TaskStateInternal @@ -29,21 +29,25 @@ import org.gradle.api.internal.tasks.properties.TaskProperties import org.gradle.api.tasks.StopActionException import org.gradle.api.tasks.StopExecutionException import org.gradle.api.tasks.TaskExecutionException +import org.gradle.caching.internal.controller.BuildCacheController import org.gradle.groovy.scripts.ScriptSource import org.gradle.initialization.DefaultBuildCancellationToken import org.gradle.internal.exceptions.DefaultMultiCauseException import org.gradle.internal.exceptions.MultiCauseException +import org.gradle.internal.execution.CachingResult +import org.gradle.internal.execution.IncrementalContext import org.gradle.internal.execution.OutputChangeListener import org.gradle.internal.execution.history.ExecutionHistoryStore -import org.gradle.internal.execution.history.OutputFilesRepository +import org.gradle.internal.execution.history.changes.DefaultExecutionStateChangeDetector import org.gradle.internal.execution.impl.DefaultWorkExecutor -import org.gradle.internal.execution.impl.steps.CancelExecutionStep -import org.gradle.internal.execution.impl.steps.CatchExceptionStep -import org.gradle.internal.execution.impl.steps.Context -import org.gradle.internal.execution.impl.steps.ExecuteStep -import org.gradle.internal.execution.impl.steps.SkipUpToDateStep -import org.gradle.internal.execution.impl.steps.SnapshotOutputStep -import org.gradle.internal.execution.impl.steps.UpToDateResult +import org.gradle.internal.execution.steps.BroadcastChangingOutputsStep +import org.gradle.internal.execution.steps.CancelExecutionStep +import org.gradle.internal.execution.steps.CatchExceptionStep +import org.gradle.internal.execution.steps.ExecuteStep +import org.gradle.internal.execution.steps.ResolveCachingStateStep +import org.gradle.internal.execution.steps.ResolveChangesStep +import org.gradle.internal.execution.steps.SkipUpToDateStep +import org.gradle.internal.execution.steps.SnapshotOutputsStep import org.gradle.internal.id.UniqueId import org.gradle.internal.operations.BuildOperationContext import org.gradle.internal.operations.BuildOperationExecutor @@ -56,8 +60,8 @@ import static java.util.Collections.emptyList class ExecuteActionsTaskExecuterTest extends Specification { def task = Mock(TaskInternal) - def action1 = Mock(ContextAwareTaskAction) - def action2 = Mock(ContextAwareTaskAction) + def action1 = Mock(InputChangesAwareTaskAction) + def action2 = Mock(InputChangesAwareTaskAction) def state = new TaskStateInternal() def executionContext = Mock(TaskExecutionContext) def taskProperties = Mock(TaskProperties) @@ -65,32 +69,46 @@ class ExecuteActionsTaskExecuterTest extends Specification { def standardOutputCapture = Mock(StandardOutputCapture) def buildOperationExecutor = Mock(BuildOperationExecutor) def asyncWorkTracker = Mock(AsyncWorkTracker) - def stringInterner = new StringInterner() def taskFingerprinter = Stub(TaskFingerprinter) { fingerprintTaskFiles(task, _) >> ImmutableSortedMap.of() } def executionHistoryStore = Mock(ExecutionHistoryStore) - def outputFilesRepository = Stub(OutputFilesRepository) { - isGeneratedByGradle(_) >> true - } def buildId = UniqueId.generate() def actionListener = Mock(TaskActionListener) def outputChangeListener = Mock(OutputChangeListener) def cancellationToken = new DefaultBuildCancellationToken() - def workExecutor = new DefaultWorkExecutor( - new SkipUpToDateStep( - new SnapshotOutputStep( - buildId, - new CatchExceptionStep( - new CancelExecutionStep(cancellationToken, - new ExecuteStep(outputChangeListener) + def changeDetector = new DefaultExecutionStateChangeDetector() + def taskCacheabilityResolver = Mock(TaskCacheabilityResolver) + def buildCacheController = Mock(BuildCacheController) + def workExecutor = new DefaultWorkExecutor( + new ResolveCachingStateStep(buildCacheController, false, + new ResolveChangesStep<>(changeDetector, + new SkipUpToDateStep<>( + new BroadcastChangingOutputsStep<>(outputChangeListener, + new SnapshotOutputsStep<>(buildId, + new CatchExceptionStep<>( + new CancelExecutionStep<>(cancellationToken, + new ExecuteStep<>() + ) + ) + ) ) ) ) ) ) - def executer = new ExecuteActionsTaskExecuter(false, taskFingerprinter, executionHistoryStore, outputFilesRepository, buildOperationExecutor, asyncWorkTracker, actionListener, workExecutor) + def executer = new ExecuteActionsTaskExecuter( + false, + false, + taskFingerprinter, + executionHistoryStore, + buildOperationExecutor, + asyncWorkTracker, + actionListener, + taskCacheabilityResolver, + workExecutor + ) def setup() { ProjectInternal project = Mock(ProjectInternal) @@ -100,7 +118,8 @@ class ExecuteActionsTaskExecuterTest extends Specification { task.getStandardOutputCapture() >> standardOutputCapture executionContext.getOutputFilesBeforeExecution() >> ImmutableSortedMap.of() executionContext.getOverlappingOutputs() >> Optional.empty() - executionContext.getExecutionStateChanges() >> Optional.empty() + executionContext.getTaskExecutionMode() >> TaskExecutionMode.INCREMENTAL + executionContext.getBeforeExecutionState() >> Optional.empty() executionContext.getTaskProperties() >> taskProperties taskProperties.getOutputFileProperties() >> ImmutableSortedSet.of() @@ -143,15 +162,13 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action1.contextualise(executionContext) - then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: 1 * action1.execute(task) >> { assert state.executing } then: - 1 * action1.releaseContext() + 1 * action1.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) then: @@ -159,13 +176,11 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action2.contextualise(executionContext) - then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: 1 * action2.execute(task) then: - 1 * action2.releaseContext() + 1 * action2.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) then: @@ -194,8 +209,6 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() - then: - 1 * action1.contextualise(executionContext) then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: @@ -203,7 +216,7 @@ class ExecuteActionsTaskExecuterTest extends Specification { task.getActions().add(action2) } then: - 1 * action1.releaseContext() + 1 * action1.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) then: @@ -227,11 +240,9 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action1.contextualise(executionContext) - then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: - 1 * action1.releaseContext() + 1 * action1.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) then: @@ -260,13 +271,11 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action1.contextualise(executionContext) - then: 1 * action1.execute(task) >> { throw new StopExecutionException('stop') } then: - 1 * action1.releaseContext() + 1 * action1.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) then: @@ -291,15 +300,13 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action1.contextualise(executionContext) - then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: 1 * action1.execute(task) >> { throw new StopActionException('stop') } then: - 1 * action1.releaseContext() + 1 * action1.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) then: @@ -307,13 +314,11 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action2.contextualise(executionContext) - then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: 1 * action2.execute(task) then: - 1 * action2.releaseContext() + 1 * action2.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) then: @@ -339,11 +344,9 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action1.contextualise(executionContext) - then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: - 1 * action1.releaseContext() + 1 * action1.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) >> { throw new DefaultMultiCauseException("mock failures", new RuntimeException("failure 1"), new RuntimeException("failure 2")) @@ -379,11 +382,9 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action1.contextualise(executionContext) - then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: - 1 * action1.releaseContext() + 1 * action1.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) >> { throw new DefaultMultiCauseException("mock failures", new RuntimeException("failure 1"), new RuntimeException("failure 2")) @@ -418,11 +419,9 @@ class ExecuteActionsTaskExecuterTest extends Specification { then: 1 * standardOutputCapture.start() then: - 1 * action1.contextualise(executionContext) - then: 1 * buildOperationExecutor.run(_ as RunnableBuildOperation) >> { args -> args[0].run(Stub(BuildOperationContext)) } then: - 1 * action1.releaseContext() + 1 * action1.clearInputChanges() then: 1 * asyncWorkTracker.waitForCompletion(_, true) >> { throw new DefaultMultiCauseException("mock failures", failure) diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveBeforeExecutionOutputsTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveBeforeExecutionOutputsTaskExecuterTest.groovy index 9482034012442..8cce5cfde7d00 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveBeforeExecutionOutputsTaskExecuterTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveBeforeExecutionOutputsTaskExecuterTest.groovy @@ -66,7 +66,7 @@ class ResolveBeforeExecutionOutputsTaskExecuterTest extends Specification { then: 1 * context.setOutputFilesBeforeExecution(outputFilesBeforeExecution) - 1 * delegate.execute(task, state, context) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * delegate.execute(task, state, context) >> TaskExecuterResult.WITHOUT_OUTPUTS 0 * context.setOverlappingOutputs(_ as OverlappingOutputs) } @@ -90,6 +90,6 @@ class ResolveBeforeExecutionOutputsTaskExecuterTest extends Specification { then: 1 * context.setOutputFilesBeforeExecution(outputFilesBeforeExecution) - 1 * delegate.execute(task, state, context) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * delegate.execute(task, state, context) >> TaskExecuterResult.WITHOUT_OUTPUTS } } diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveBuildCacheKeyExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveBuildCacheKeyExecuterTest.groovy deleted file mode 100644 index 3f86bcf7ccad9..0000000000000 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveBuildCacheKeyExecuterTest.groovy +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks.execution - -import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableSortedMap -import com.google.common.collect.ImmutableSortedSet -import org.gradle.api.internal.TaskInternal -import org.gradle.api.internal.tasks.TaskExecuter -import org.gradle.api.internal.tasks.TaskExecuterResult -import org.gradle.api.internal.tasks.TaskExecutionContext -import org.gradle.api.internal.tasks.TaskStateInternal -import org.gradle.api.internal.tasks.properties.TaskProperties -import org.gradle.caching.internal.tasks.BuildCacheKeyInputs -import org.gradle.caching.internal.tasks.TaskCacheKeyCalculator -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey -import org.gradle.internal.execution.history.BeforeExecutionState -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint -import org.gradle.internal.hash.HashCode -import org.gradle.internal.snapshot.impl.ImplementationSnapshot -import org.gradle.testing.internal.util.Specification - -class ResolveBuildCacheKeyExecuterTest extends Specification { - - def taskState = Mock(TaskStateInternal) - def task = Mock(TaskInternal) - def taskContext = Mock(TaskExecutionContext) - def beforeExecution = Mock(BeforeExecutionState) - def taskProperties = Mock(TaskProperties) - def delegate = Mock(TaskExecuter) - def calculator = Mock(TaskCacheKeyCalculator) - def executer = new ResolveBuildCacheKeyExecuter(calculator, false, delegate) - def cacheKey = Mock(TaskOutputCachingBuildCacheKey) - - def "calculates build cache key"() { - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskContext.getTaskProperties() >> taskProperties - _ * taskContext.getBeforeExecutionState() >> Optional.of(beforeExecution) - 1 * calculator.calculate(task, beforeExecution, taskProperties, false) >> cacheKey - - then: - 1 * taskProperties.hasDeclaredOutputs() >> true - 1 * cacheKey.isValid() >> true - 1 * cacheKey.getHashCode() >> "0123456789abcdef" - - then: - 1 * taskContext.setBuildCacheKey(cacheKey) - - then: - 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT - 0 * _ - } - - def "propagates exceptions if cache key cannot be calculated"() { - def failure = new RuntimeException("Bad cache key") - - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskContext.getTaskProperties() >> taskProperties - _ * taskContext.getBeforeExecutionState() >> Optional.of(beforeExecution) - 1 * calculator.calculate(task, beforeExecution, taskProperties, false) >> { - throw failure - } - 0 * _ - - def ex = thrown RuntimeException - ex.is(failure) - } - - def "does not calculate cache key when task has no outputs"() { - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskContext.getTaskProperties() >> taskProperties - _ * taskContext.getBeforeExecutionState() >> Optional.empty() - 0 * calculator.calculate(_ as TaskInternal, _ as BeforeExecutionState, _ as TaskProperties, _ as boolean) - - then: - 1 * taskContext.setBuildCacheKey({ TaskOutputCachingBuildCacheKey key -> !key.valid } as TaskOutputCachingBuildCacheKey) - - then: - 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT - 0 * _ - } - - def "adapts key to result interface"() { - given: - def inputs = Mock(BuildCacheKeyInputs) - def key = Mock(TaskOutputCachingBuildCacheKey) { - getInputs() >> inputs - } - def adapter = new SnapshotTaskInputsMeasuringTaskExecuter.OperationResultImpl(key) - - when: - inputs.inputValueHashes >> ImmutableSortedMap.copyOf(b: HashCode.fromInt(0x000000bb), a: HashCode.fromInt(0x000000aa)) - inputs.inputFiles >> ImmutableSortedMap.copyOf(c: { getHash: { HashCode.fromInt(0x000000cc) } } as CurrentFileCollectionFingerprint) - - then: - adapter.inputValueHashesBytes.collectEntries { [(it.key):HashCode.fromBytes(it.value).toString()] } == [a: "000000aa", b: "000000bb"] - - when: - inputs.nonCacheableInputProperties >> ImmutableSortedMap.of("bean", "Implementation loaded by unknown classloader.", "someOtherBean", "Implementation implemented by Java Lambda.") - then: - adapter.inputPropertiesLoadedByUnknownClassLoader == ["bean", "someOtherBean"] as SortedSet - - when: - inputs.taskImplementation >> ImplementationSnapshot.of("org.gradle.TaskType", HashCode.fromInt(0x000000cc)) - then: - HashCode.fromBytes(adapter.classLoaderHashBytes).toString() == "000000cc" - - when: - inputs.actionImplementations >> ImmutableList.copyOf([ImplementationSnapshot.of("foo", HashCode.fromInt(0x000000ee)), ImplementationSnapshot.of("bar", HashCode.fromInt(0x000000dd))]) - then: - adapter.actionClassLoaderHashesBytes.collect{ HashCode.fromBytes(it).toString() } == ["000000ee", "000000dd"] - adapter.actionClassNames == ["foo", "bar"] - - when: - inputs.outputPropertyNames >> ImmutableSortedSet.copyOf(["2", "1"]) - then: - adapter.outputPropertyNames == ["1", "2"] - - when: - key.hashCodeBytes >> HashCode.fromInt(0x000000ff).toByteArray() - key.valid >> true - then: - HashCode.fromBytes(adapter.hashBytes).toString() == "000000ff" - } -} diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveTaskExecutionModeExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveTaskExecutionModeExecuterTest.groovy index 27eae191a07ba..7b2fd82601ca9 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveTaskExecutionModeExecuterTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveTaskExecutionModeExecuterTest.groovy @@ -74,7 +74,7 @@ class ResolveTaskExecutionModeExecuterTest extends Specification { 1 * outputs.visitRegisteredProperties(_) then: 'delegate is executed' - 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.WITHOUT_OUTPUTS then: 'task artifact state is removed from taskContext' 1 * outputs.setPreviousOutputFiles(null) diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveTaskOutputCachingStateExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveTaskOutputCachingStateExecuterTest.groovy deleted file mode 100644 index 9e31812ed5285..0000000000000 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ResolveTaskOutputCachingStateExecuterTest.groovy +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks.execution - -import com.google.common.collect.ImmutableSortedSet -import org.gradle.api.GradleException -import org.gradle.api.internal.OverlappingOutputs -import org.gradle.api.internal.TaskInternal -import org.gradle.api.internal.TaskOutputCachingState -import org.gradle.api.internal.tasks.TaskExecuter -import org.gradle.api.internal.tasks.TaskExecuterResult -import org.gradle.api.internal.tasks.TaskExecutionContext -import org.gradle.api.internal.tasks.TaskOutputCachingDisabledReasonCategory -import org.gradle.api.internal.tasks.TaskStateInternal -import org.gradle.api.internal.tasks.properties.CacheableOutputFilePropertySpec -import org.gradle.api.internal.tasks.properties.OutputFilePropertySpec -import org.gradle.api.specs.Spec -import org.gradle.caching.internal.tasks.DefaultTaskOutputCachingBuildCacheKeyBuilder -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey -import org.gradle.internal.file.RelativeFilePathResolver -import org.gradle.internal.hash.HashCode -import org.gradle.internal.snapshot.impl.ImplementationSnapshot -import org.gradle.testing.internal.util.Specification - -import javax.annotation.Nullable - -class ResolveTaskOutputCachingStateExecuterTest extends Specification { - - def task = Stub(TaskInternal) - def cacheableOutputProperty = Mock(CacheableOutputFilePropertySpec) - def cacheKey = Stub(TaskOutputCachingBuildCacheKey) { - isValid() >> true - } - def relativeFilePathResolver = Mock(RelativeFilePathResolver) - - def "error message contains which cacheIf spec failed to evaluate"() { - when: - resolveCachingState( - [cacheableOutputProperty], - cacheKey, - task, - [spec({ throw new RuntimeException() }, "Exception is thrown")], - [], - null - ) - - then: - GradleException e = thrown() - e.message.contains("Could not evaluate spec for 'Exception is thrown'.") - } - - def "error message contains which doNotCacheIf spec failed to evaluate"() { - when: - resolveCachingState( - [cacheableOutputProperty], - cacheKey, - task, - [spec({ true })], - [spec({ "throw new RuntimeException()" }, "Exception is thrown")], - null - ) - - then: - GradleException e = thrown() - e.message.contains("Could not evaluate spec for 'Exception is thrown'.") - } - - def "report no reason if the task is cacheable"() { - when: - def state = resolveCachingState( - [cacheableOutputProperty], - cacheKey, - task, - [spec({ true })], - [], - null - ) - - then: - state.enabled - state.disabledReasonCategory == null - state.disabledReason == null - } - - def "caching is disabled with no outputs"() { - when: - def state = resolveCachingState( - [], - cacheKey, - task, - [spec({ true })], - [], - null - ) - - then: - !state.enabled - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.NO_OUTPUTS_DECLARED - state.disabledReason == "No outputs declared" - } - - def "no cacheIf() means no caching"() { - when: - def state = resolveCachingState( - [cacheableOutputProperty], - cacheKey, - task, - [], - [], - null - ) - - then: - !state.enabled - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.NOT_ENABLED_FOR_TASK - state.disabledReason == "Caching has not been enabled for the task" - } - - def "can turn caching off via cacheIf()"() { - when: - def state = resolveCachingState( - [cacheableOutputProperty], - cacheKey, - task, - [spec({ false }, "Cacheable test")], - [], - null - ) - - then: - !state.enabled - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.CACHE_IF_SPEC_NOT_SATISFIED - state.disabledReason == "'Cacheable test' not satisfied" - } - - def "can turn caching off via doNotCacheIf()"() { - when: - def state = resolveCachingState( - [cacheableOutputProperty], - cacheKey, - task, - [spec({ true })], - [spec({ true }, "Uncacheable test")], - null - ) - - then: - !state.enabled - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.DO_NOT_CACHE_IF_SPEC_SATISFIED - state.disabledReason == "'Uncacheable test' satisfied" - } - - def "caching is disabled for non-cacheable file outputs is reported"() { - when: - def state = resolveCachingState( - [Stub(OutputFilePropertySpec) { - getPropertyName() >> "non-cacheable property" - }], - cacheKey, - task, - [spec({ true })], - [], - null - ) - then: - !state.enabled - state.disabledReason == "Output property 'non-cacheable property' contains a file tree" - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TREE_OUTPUT - } - - def "caching is disabled when cache key is invalid because of invalid task implementation"() { - def builder = new DefaultTaskOutputCachingBuildCacheKeyBuilder() - builder.appendTaskImplementation(ImplementationSnapshot.of("org.gradle.TaskType", null)) - def invalidBuildCacheKey = builder.build() - - when: - def state = resolveCachingState( - [cacheableOutputProperty], - invalidBuildCacheKey, - task, - [spec({ true })], - [], - null - ) - - then: - !state.enabled - state.disabledReason == "Task class was loaded with an unknown classloader (class 'org.gradle.TaskType')." - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TASK_IMPLEMENTATION - } - - def "caching is disabled when cache key is invalid because of invalid task action"() { - def builder = new DefaultTaskOutputCachingBuildCacheKeyBuilder() - builder.appendTaskActionImplementations([ImplementationSnapshot.of('org.my.package.MyPlugin$$Lambda$1/23246642345', HashCode.fromInt(12345))]) - def invalidBuildCacheKey = builder.build() - - when: - def state = resolveCachingState( - [cacheableOutputProperty], - invalidBuildCacheKey, - task, - [spec({ true })], - [], - null - ) - - then: - !state.enabled - state.disabledReason == 'Task action was implemented by the Java lambda \'org.my.package.MyPlugin$$Lambda$1/23246642345\'. Using Java lambdas is not supported, use an (anonymous) inner class instead.' - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_TASK_ACTION - } - - def "caching is disabled when cache key is invalid because of invalid input"() { - def builder = new DefaultTaskOutputCachingBuildCacheKeyBuilder() - builder.inputPropertyNotCacheable("someProperty", 'was implemented by the Java lambda \'org.my.package.MyPlugin$$Lambda$5/342523421\'') - def invalidBuildCacheKey = builder.build() - - when: - def state = resolveCachingState( - [cacheableOutputProperty], - invalidBuildCacheKey, - task, - [spec({ true })], - [], - null - ) - - then: - !state.enabled - state.disabledReason == 'Non-cacheable inputs: property \'someProperty\' was implemented by the Java lambda \'org.my.package.MyPlugin$$Lambda$5/342523421\'' - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.NON_CACHEABLE_INPUTS - } - - def "caching is disabled when cache key is invalid because of overlapping outputs"() { - def overlappingOutputs = new OverlappingOutputs("someProperty", "path/to/outputFile") - - when: - def state = resolveCachingState( - [cacheableOutputProperty], - cacheKey, - task, - [spec({ true })], - [], - overlappingOutputs - ) - - then: - 1 * relativeFilePathResolver.resolveAsRelativePath(overlappingOutputs.overlappedFilePath) >> "relative/path" - - !state.enabled - state.disabledReason == "Gradle does not know how file 'relative/path' was created (output property 'someProperty'). Task output caching requires exclusive access to output paths to guarantee correctness." - state.disabledReasonCategory == TaskOutputCachingDisabledReasonCategory.OVERLAPPING_OUTPUTS - } - - def "when build cache is disabled, state is DISABLED"() { - def taskState = Mock(TaskStateInternal) - def taskContext = Mock(TaskExecutionContext) - def delegate = Mock(TaskExecuter) - def executer = new ResolveTaskOutputCachingStateExecuter(false, relativeFilePathResolver, delegate) - - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskState.setTaskOutputCaching({ !it.enabled } as TaskOutputCachingState) - - then: - 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT - 0 * _ - } - - static def spec(Spec spec, String description = "test cacheIf()") { - new SelfDescribingSpec(spec, description) - } - - TaskOutputCachingState resolveCachingState( - Collection outputFileProperties, - TaskOutputCachingBuildCacheKey buildCacheKey, - TaskInternal task, - Collection> cacheIfSpecs, - Collection> doNotCacheIfSpecs, - @Nullable OverlappingOutputs overlappingOutputs - ) { - return ResolveTaskOutputCachingStateExecuter.resolveCachingState( - !outputFileProperties.isEmpty(), - ImmutableSortedSet.copyOf(outputFileProperties), - buildCacheKey, - task, - cacheIfSpecs, - doNotCacheIfSpecs, - overlappingOutputs, - relativeFilePathResolver - ) - } -} diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipCachedTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipCachedTaskExecuterTest.groovy deleted file mode 100644 index 4920f96cccbca..0000000000000 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipCachedTaskExecuterTest.groovy +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.api.internal.tasks.execution - -import com.google.common.collect.ImmutableSortedSet -import org.gradle.api.Project -import org.gradle.api.file.FileCollection -import org.gradle.api.internal.TaskInternal -import org.gradle.api.internal.TaskOutputCachingState -import org.gradle.api.internal.changedetection.TaskExecutionMode -import org.gradle.api.internal.tasks.TaskExecuter -import org.gradle.api.internal.tasks.TaskExecuterResult -import org.gradle.api.internal.tasks.TaskExecutionContext -import org.gradle.api.internal.tasks.TaskStateInternal -import org.gradle.api.internal.tasks.properties.TaskProperties -import org.gradle.caching.internal.command.BuildCacheCommandFactory -import org.gradle.caching.internal.controller.BuildCacheController -import org.gradle.caching.internal.controller.BuildCacheStoreCommand -import org.gradle.caching.internal.packaging.UnrecoverableUnpackingException -import org.gradle.caching.internal.tasks.TaskOutputCachingBuildCacheKey -import org.gradle.testing.internal.util.Specification -import spock.lang.Ignore - -// TODO rewrite this better before the PR is merged -@Ignore("I, lptr, promise to rewrite this better before this PR is merged") -class SkipCachedTaskExecuterTest extends Specification { - def delegate = Mock(TaskExecuter) - def project = Mock(Project) - def projectDir = Mock(File) - def taskOutputCaching = Mock(TaskOutputCachingState) - def localStateFiles = Stub(FileCollection) - def taskProperties = Mock(TaskProperties) - def task = Stub(TaskInternal) - def taskState = Mock(TaskStateInternal) - def taskContext = Mock(TaskExecutionContext) - def taskExecutionMode = Mock(TaskExecutionMode) - def buildCacheController = Mock(BuildCacheController) - def cacheKey = Mock(TaskOutputCachingBuildCacheKey) - def storeCommand = Mock(BuildCacheStoreCommand) - def buildCacheCommandFactory = Mock(BuildCacheCommandFactory) - def outputFingerprints = [:] - - // def executer = new SkipCachedTaskExecuter(buildCacheController, outputChangeListener, buildCacheCommandFactory, delegate) - - def "executes task and stores result when use of cached result is not allowed"() { - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskContext.taskProperties >> taskProperties - 1 * taskContext.buildCacheKey >> cacheKey - interaction { cachingEnabled() } - - then: - 1 * taskProperties.getOutputFileProperties() >> ImmutableSortedSet.of() - 1 * taskContext.getTaskExecutionMode() >> taskExecutionMode - 1 * taskExecutionMode.isAllowedToUseCachedResults() >> false - - then: - 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT - - then: - 1 * taskState.getFailure() >> null - - then: - 1 * taskContext.getTaskExecutionMode() >> taskExecutionMode - 1 * buildCacheCommandFactory.createStore(cacheKey, task, outputFingerprints, 1) >> storeCommand - - then: - 1 * buildCacheController.store(storeCommand) - 0 * _ - } - - def "executes task and does not cache results when caching was disabled"() { - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskContext.taskProperties >> taskProperties - 1 * taskContext.buildCacheKey >> cacheKey - interaction { cachingDisabled() } - - then: - 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT - 0 * _ - } - - def "stores result when cache backend throws recoverable exception while loading result"() { - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskContext.taskProperties >> taskProperties - 1 * taskContext.buildCacheKey >> cacheKey - interaction { cachingEnabled() } - - then: - 1 * taskProperties.outputFileProperties >> ImmutableSortedSet.of() - 1 * taskProperties.localStateFiles >> localStateFiles - 1 * taskContext.getTaskExecutionMode() >> taskExecutionMode - 1 * taskExecutionMode.isAllowedToUseCachedResults() >> true - - then: - 1 * buildCacheCommandFactory.createLoad(*_) - 1 * buildCacheController.load(_) >> { throw new RuntimeException("unknown error") } - - then: - 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT - - then: - 1 * taskState.getFailure() >> null - - then: - 1 * taskContext.getTaskExecutionMode() >> taskExecutionMode - 1 * buildCacheCommandFactory.createStore(cacheKey, task, outputFingerprints, 1) >> storeCommand - - then: - 1 * buildCacheController.store(storeCommand) - 0 * _ - } - - def "fails when cache backend throws unrecoverable exception while finding result"() { - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskContext.taskProperties >> taskProperties - 1 * taskContext.buildCacheKey >> cacheKey - interaction { cachingEnabled() } - - then: - 1 * taskProperties.outputFileProperties >> ImmutableSortedSet.of() - 1 * taskProperties.localStateFiles >> localStateFiles - 1 * taskContext.getTaskExecutionMode() >> taskExecutionMode - 1 * taskExecutionMode.isAllowedToUseCachedResults() >> true - - then: - 1 * buildCacheCommandFactory.createLoad(*_) - 1 * buildCacheController.load(_) >> { throw new UnrecoverableUnpackingException("unknown error") } - - then: - 0 * _ - then: - def e = thrown UnrecoverableUnpackingException - e.message == "unknown error" - } - - def "does not fail when cache backend throws exception while storing cached result"() { - when: - executer.execute(task, taskState, taskContext) - - then: - 1 * taskContext.taskProperties >> taskProperties - 1 * taskContext.buildCacheKey >> cacheKey - interaction { cachingEnabled() } - - then: - 1 * taskProperties.outputFileProperties >> ImmutableSortedSet.of() - 1 * taskProperties.localStateFiles >> localStateFiles - 1 * taskContext.getTaskExecutionMode() >> taskExecutionMode - 1 * taskExecutionMode.isAllowedToUseCachedResults() >> true - - then: - 1 * buildCacheCommandFactory.createLoad(*_) - 1 * buildCacheController.load(_) - - then: - 1 * delegate.execute(task, taskState, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT - - then: - 1 * taskState.getFailure() >> null - - then: - 1 * cacheKey.getDisplayName() >> "cache key" - 1 * taskContext.getTaskExecutionMode() >> taskExecutionMode - 1 * buildCacheCommandFactory.createStore(*_) - 1 * buildCacheController.store(_) >> { throw new RuntimeException("unknown error") } - - then: - 0 * _ - } - - private void cachingEnabled() { - 1 * taskState.getTaskOutputCaching() >> taskOutputCaching - 1 * taskOutputCaching.isEnabled() >> true - } - - private void cachingDisabled() { - 1 * taskState.getTaskOutputCaching() >> taskOutputCaching - 1 * taskOutputCaching.isEnabled() >> false - } -} diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuterTest.groovy index 720cf7cf00327..5f08e9842e603 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuterTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipEmptySourceFilesTaskExecuterTest.groovy @@ -290,7 +290,7 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification { then: 1 * taskProperties.getInputFiles() >> taskFiles - 1 * target.execute(task, state, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * target.execute(task, state, taskContext) >> TaskExecuterResult.WITHOUT_OUTPUTS 1 * taskInputsListener.onExecute(task, taskFiles) then: @@ -308,7 +308,7 @@ class SkipEmptySourceFilesTaskExecuterTest extends Specification { then: 1 * taskProperties.getInputFiles() >> taskFiles - 1 * target.execute(task, state, taskContext) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * target.execute(task, state, taskContext) >> TaskExecuterResult.WITHOUT_OUTPUTS 1 * taskInputsListener.onExecute(task, taskFiles) then: diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuterTest.groovy index 6cf8aa493e0f7..f7e9e0540e0dd 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuterTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/SkipOnlyIfTaskExecuterTest.groovy @@ -64,7 +64,7 @@ class SkipOnlyIfTaskExecuterTest extends Specification { then: 1 * spec.isSatisfiedBy(task) >> true - 1 * delegate.execute(task, state, executionContext) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * delegate.execute(task, state, executionContext) >> TaskExecuterResult.WITHOUT_OUTPUTS noMoreInteractions() } diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuterTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuterTest.groovy index cefc3084d4310..cbc3a5c6b2ea0 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuterTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/execution/ValidatingTaskExecuterTest.groovy @@ -48,7 +48,7 @@ class ValidatingTaskExecuterTest extends Specification { 1 * task.getProject() >> project 1 * executionContext.getTaskProperties() >> taskProperties 1 * taskProperties.validate(_) - 1 * target.execute(task, state, executionContext) >> TaskExecuterResult.NO_REUSED_OUTPUT + 1 * target.execute(task, state, executionContext) >> TaskExecuterResult.WITHOUT_OUTPUTS 0 * _ } @@ -59,7 +59,7 @@ class ValidatingTaskExecuterTest extends Specification { then: 1 * task.getProject() >> project 1 * executionContext.getTaskProperties() >> taskProperties - 1 * taskProperties.validate(_) >> { TaskValidationContext context -> context.recordValidationMessage('failure') } + 1 * taskProperties.validate(_) >> { TaskValidationContext context -> context.visitError('failure') } 1 * state.setOutcome(!null as Throwable) >> { def failure = it[0] assert failure instanceof TaskValidationException @@ -78,8 +78,8 @@ class ValidatingTaskExecuterTest extends Specification { 1 * task.getProject() >> project 1 * executionContext.getTaskProperties() >> taskProperties 1 * taskProperties.validate(_) >> { TaskValidationContext context -> - context.recordValidationMessage('failure1') - context.recordValidationMessage('failure2') + context.visitError('failure1') + context.visitError('failure2') } 1 * state.setOutcome(!null as Throwable) >> { def failure = it[0] diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/AssociatedTransformAction.java b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/CustomCacheable.java similarity index 69% rename from subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/AssociatedTransformAction.java rename to subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/CustomCacheable.java index 009d1e7f07e66..98fc777c259ad 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/AssociatedTransformAction.java +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/CustomCacheable.java @@ -14,23 +14,14 @@ * limitations under the License. */ -package org.gradle.api.artifacts.transform; - -import org.gradle.api.Incubating; +package org.gradle.api.internal.tasks.properties; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** - * Attached to an artifact transform parameter type to declare the corresponding {@link TransformAction} implementation to use. - * - * @since 5.3 - */ -@Incubating +@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE}) -public @interface AssociatedTransformAction { - Class value(); +@interface CustomCacheable { } diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/DefaultPropertyWalkerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/DefaultPropertyWalkerTest.groovy index 518356fe3dcb6..27a7f7678f3da 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/DefaultPropertyWalkerTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/DefaultPropertyWalkerTest.groovy @@ -53,11 +53,11 @@ class DefaultPropertyWalkerTest extends AbstractProjectBuilderSpec { then: _ * visitor.visitOutputFilePropertiesOnly() >> false 1 * visitor.visitInputProperty('myProperty', { it.call() == 'myValue' }, false) - 1 * visitor.visitInputFileProperty('inputFile', _, _, _, _, InputFilePropertyType.FILE) - 1 * visitor.visitInputFileProperty('inputFiles', _, _, _, _, InputFilePropertyType.FILES) + 1 * visitor.visitInputFileProperty('inputFile', _, _, _, _, _, InputFilePropertyType.FILE) + 1 * visitor.visitInputFileProperty('inputFiles', _, _, _, _, _, InputFilePropertyType.FILES) 1 * visitor.visitInputProperty('bean', { it.call() == NestedBean }, false) 1 * visitor.visitInputProperty('bean.nestedInput', { it.call() == 'nested' }, false) - 1 * visitor.visitInputFileProperty('bean.inputDir', _, _, _, _, InputFilePropertyType.DIRECTORY) + 1 * visitor.visitInputFileProperty('bean.inputDir', _, _, _, _, _, InputFilePropertyType.DIRECTORY) 1 * visitor.visitOutputFileProperty('outputFile', false, { it.call().path == 'output' }, OutputFilePropertyType.FILE) 1 * visitor.visitOutputFileProperty('bean.outputDir', false, { it.call().path == 'outputDir' }, OutputFilePropertyType.DIRECTORY) @@ -195,7 +195,7 @@ class DefaultPropertyWalkerTest extends AbstractProjectBuilderSpec { _ * visitor.visitOutputFilePropertiesOnly() >> false 1 * visitor.visitInputProperty("nested" , _, false) 1 * visitor.visitInputProperty("nested.nestedInput", _, false) - 1 * visitor.visitInputFileProperty("nested.inputDir", _, _, _, _, InputFilePropertyType.DIRECTORY) + 1 * visitor.visitInputFileProperty("nested.inputDir", _, _, _, _, _, InputFilePropertyType.DIRECTORY) 1 * visitor.visitOutputFileProperty("nested.outputDir", false, _, OutputFilePropertyType.DIRECTORY) 0 * _ @@ -228,6 +228,6 @@ class DefaultPropertyWalkerTest extends AbstractProjectBuilderSpec { } private visitProperties(TaskInternal task) { - new DefaultPropertyWalker(new DefaultTypeMetadataStore(services.getAll(PropertyAnnotationHandler), [] as Set, new TestCrossBuildInMemoryCacheFactory())).visitProperties(task, validationContext, visitor) + new DefaultPropertyWalker(new DefaultTypeMetadataStore(services.getAll(PropertyAnnotationHandler), [] as Set, [] as List, new TestCrossBuildInMemoryCacheFactory())).visitProperties(task, validationContext, visitor) } } diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/DefaultTypeMetadataStoreTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/DefaultTypeMetadataStoreTest.groovy index da3e474e0c9aa..f7814b68feeeb 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/DefaultTypeMetadataStoreTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/DefaultTypeMetadataStoreTest.groovy @@ -26,19 +26,24 @@ import org.gradle.api.internal.HasConvention import org.gradle.api.internal.IConventionAware import org.gradle.api.internal.tasks.properties.annotations.ClasspathPropertyAnnotationHandler import org.gradle.api.internal.tasks.properties.annotations.PropertyAnnotationHandler +import org.gradle.api.internal.tasks.properties.annotations.TypeAnnotationHandler +import org.gradle.api.model.ReplacedBy import org.gradle.api.plugins.ExtensionAware import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Console +import org.gradle.api.tasks.Destroys import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.LocalState import org.gradle.api.tasks.OutputDirectories import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFiles import org.gradle.cache.internal.TestCrossBuildInMemoryCacheFactory +import org.gradle.internal.reflect.ParameterValidationContext import org.gradle.internal.reflect.PropertyMetadata import org.gradle.internal.scripts.ScriptOrigin import org.gradle.internal.service.ServiceRegistryBuilder @@ -54,16 +59,16 @@ import java.lang.annotation.Annotation class DefaultTypeMetadataStoreTest extends Specification { private static final List> PROCESSED_PROPERTY_TYPE_ANNOTATIONS = [ - InputFile, InputFiles, InputDirectory, OutputFile, OutputDirectory, OutputFiles, OutputDirectories + InputFile, InputFiles, InputDirectory, OutputFile, OutputDirectory, OutputFiles, OutputDirectories, Destroys, LocalState ] private static final List> UNPROCESSED_PROPERTY_TYPE_ANNOTATIONS = [ - Console, Internal, Inject + Console, Internal, ReplacedBy ] @Shared GroovyClassLoader groovyClassLoader def services = ServiceRegistryBuilder.builder().provider(new ExecutionGlobalServices()).build() - def metadataStore = new DefaultTypeMetadataStore(services.getAll(PropertyAnnotationHandler), [] as Set, new TestCrossBuildInMemoryCacheFactory()) + def metadataStore = new DefaultTypeMetadataStore(services.getAll(PropertyAnnotationHandler), [] as Set, [] as List, new TestCrossBuildInMemoryCacheFactory()) def setupSpec() { groovyClassLoader = new GroovyClassLoader(getClass().classLoader) @@ -73,30 +78,20 @@ class DefaultTypeMetadataStoreTest extends Specification { @SearchPath FileCollection searchPath } - class SearchPathAnnotationHandler implements PropertyAnnotationHandler { - - @Override - Class getAnnotationType() { - SearchPath - } - - @Override - boolean shouldVisit(PropertyVisitor visitor) { - return true - } - - @Override - void visitPropertyValue(String propertyName, PropertyValue value, PropertyMetadata propertyMetadata, PropertyVisitor visitor, BeanPropertyContext context) { - } + @CustomCacheable + static class TypeWithCustomAnnotation { } - def "can use custom annotation processor"() { - def annotationHandler = new SearchPathAnnotationHandler() - def metadataStore = new DefaultTypeMetadataStore([annotationHandler], [] as Set, new TestCrossBuildInMemoryCacheFactory()) + def "can use custom annotation handler"() { + def annotationHandler = Stub(PropertyAnnotationHandler) + _ * annotationHandler.propertyRelevant >> true + _ * annotationHandler.annotationType >> SearchPath + + def metadataStore = new DefaultTypeMetadataStore([annotationHandler], [] as Set, [] as Set, new TestCrossBuildInMemoryCacheFactory()) when: def typeMetadata = metadataStore.getTypeMetadata(TaskWithCustomAnnotation) - def propertiesMetadata = typeMetadata.propertiesMetadata.findAll { !isIgnored(it) } + def propertiesMetadata = typeMetadata.propertiesMetadata then: propertiesMetadata.size() == 1 @@ -107,6 +102,68 @@ class DefaultTypeMetadataStoreTest extends Specification { collectProblems(typeMetadata).empty } + def "custom annotation handler can inspect for static property problems"() { + def annotationHandler = Stub(PropertyAnnotationHandler) + _ * annotationHandler.propertyRelevant >> true + _ * annotationHandler.annotationType >> SearchPath + _ * annotationHandler.validatePropertyMetadata(_, _) >> { PropertyMetadata metadata, ParameterValidationContext context -> + context.visitError(null, metadata.propertyName, "is broken") + } + + def metadataStore = new DefaultTypeMetadataStore([annotationHandler], [] as Set, [] as Set, new TestCrossBuildInMemoryCacheFactory()) + + when: + def typeMetadata = metadataStore.getTypeMetadata(TaskWithCustomAnnotation) + def propertiesMetadata = typeMetadata.propertiesMetadata + + then: + propertiesMetadata.size() == 1 + def propertyMetadata = propertiesMetadata.first() + propertyMetadata.propertyName == 'searchPath' + collectProblems(typeMetadata) == ["Property 'searchPath' is broken."] + } + + def "custom annotation that is not relevant can have validation problems"() { + def annotationHandler = Stub(PropertyAnnotationHandler) + _ * annotationHandler.propertyRelevant >> false + _ * annotationHandler.annotationType >> SearchPath + _ * annotationHandler.validatePropertyMetadata(_, _) >> { PropertyMetadata metadata, ParameterValidationContext context -> + context.visitError(null, metadata.propertyName, "is broken") + } + + def metadataStore = new DefaultTypeMetadataStore([annotationHandler], [] as Set, [] as Set, new TestCrossBuildInMemoryCacheFactory()) + + when: + def typeMetadata = metadataStore.getTypeMetadata(TaskWithCustomAnnotation) + def propertiesMetadata = typeMetadata.propertiesMetadata + + then: + propertiesMetadata.empty + collectProblems(typeMetadata) == ["Property 'searchPath' is broken."] + } + + def "custom type annotation handler can inspect for static type problems"() { + def annotationHandler = Stub(TypeAnnotationHandler) + _ * annotationHandler.annotationType >> CustomCacheable + _ * annotationHandler.validateTypeMetadata(_, _) >> { Class type, ParameterValidationContext context -> + context.visitError("type is broken") + } + + def metadataStore = new DefaultTypeMetadataStore([], [] as Set, [annotationHandler] as Set, new TestCrossBuildInMemoryCacheFactory()) + + when: + def taskMetadata = metadataStore.getTypeMetadata(DefaultTask) + + then: + collectProblems(taskMetadata).empty + + when: + def typeMetadata = metadataStore.getTypeMetadata(TypeWithCustomAnnotation) + + then: + collectProblems(typeMetadata) == ["type is broken"] + } + @Unroll def "can override @#parentAnnotation.simpleName property type with @#childAnnotation.simpleName"() { def parentTask = groovyClassLoader.parseClass """ @@ -155,11 +212,10 @@ class DefaultTypeMetadataStoreTest extends Specification { def parentProperty = parentMetadata.propertiesMetadata.first() def childMetadata = metadataStore.getTypeMetadata(childTask) - def childProperty = childMetadata.propertiesMetadata.first() expect: isOfType(parentProperty, processedAnnotation) - isIgnored(childProperty) + childMetadata.propertiesMetadata.empty collectProblems(parentMetadata).empty collectProblems(childMetadata).empty @@ -182,13 +238,12 @@ class DefaultTypeMetadataStoreTest extends Specification { """ def parentMetadata = metadataStore.getTypeMetadata(parentTask) - def parentProperty = parentMetadata.propertiesMetadata.first() def childMetadata = metadataStore.getTypeMetadata(childTask) def childProperty = childMetadata.propertiesMetadata.first() expect: - isIgnored(parentProperty) + parentMetadata.propertiesMetadata.empty isOfType(childProperty, processedAnnotation) collectProblems(parentMetadata).empty collectProblems(childMetadata).empty @@ -206,13 +261,13 @@ class DefaultTypeMetadataStoreTest extends Specification { // need to declare their @Classpath properties as @InputFiles as well @Issue("https://github.com/gradle/gradle/issues/913") def "@Classpath takes precedence over @InputFiles when both are declared on property"() { - def metadataStore = new DefaultTypeMetadataStore(services.getAll(PropertyAnnotationHandler) + [new ClasspathPropertyAnnotationHandler()], [] as Set, new TestCrossBuildInMemoryCacheFactory()) + def metadataStore = new DefaultTypeMetadataStore(services.getAll(PropertyAnnotationHandler) + [new ClasspathPropertyAnnotationHandler()], [] as Set, [] as Set, new TestCrossBuildInMemoryCacheFactory()) when: def typeMetadata = metadataStore.getTypeMetadata(ClasspathPropertyTask) then: - def properties = typeMetadata.propertiesMetadata.findAll { !isIgnored(it) } + def properties = typeMetadata.propertiesMetadata properties*.propertyName as List == ["inputFiles1", "inputFiles2"] properties*.propertyType as List == [Classpath, Classpath] collectProblems(typeMetadata).empty @@ -239,8 +294,11 @@ class DefaultTypeMetadataStoreTest extends Specification { @OutputFiles Set outputFiles @OutputDirectory File outputDirectory @OutputDirectories Set outputDirectories + @Destroys Set destroys + @LocalState File someCache @Inject Object injectedService @Internal Object internal + @ReplacedBy("inputString") String oldProperty @Console boolean console } @@ -249,22 +307,22 @@ class DefaultTypeMetadataStoreTest extends Specification { def properties = metadataStore.getTypeMetadata(SimpleTask).propertiesMetadata then: - nonIgnoredProperties(properties) == ["inputDirectory", "inputFile", "inputFiles", "inputString", "outputDirectories", "outputDirectory", "outputFile", "outputFiles"] + properties.propertyName.sort() == ["destroys", "inputDirectory", "inputFile", "inputFiles", "inputString", "outputDirectories", "outputDirectory", "outputFile", "outputFiles", "someCache"] } static class Unannotated extends DefaultTask { String bad1 File bad2 - @Inject String ignore1 - @Input String ignore2 + @Console String notUseful1 + @Input String useful1 } - def "ignores properties that are not annotated"() { + def "ignores properties that are not annotated or that don't do anything"() { when: def metadata = metadataStore.getTypeMetadata(Unannotated) then: - metadata.propertiesMetadata.propertyName == ["ignore1", "ignore2"] + metadata.propertiesMetadata.propertyName == ["useful1"] collectProblems(metadata) == [ "Property 'bad1' is not annotated with an input or output annotation.", "Property 'bad2' is not annotated with an input or output annotation." @@ -300,13 +358,4 @@ class DefaultTypeMetadataStoreTest extends Specification { private static boolean isOfType(PropertyMetadata metadata, Class type) { metadata.propertyType == type } - - private static boolean isIgnored(PropertyMetadata propertyMetadata) { - def propertyType = propertyMetadata.propertyType - UNPROCESSED_PROPERTY_TYPE_ANNOTATIONS.contains(propertyType) - } - - private static List nonIgnoredProperties(Collection typeMetadata) { - typeMetadata.findAll { !isIgnored(it) }*.propertyName.sort() - } } diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/InspectionSchemeFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/InspectionSchemeFactoryTest.groovy index f7f637fc3738f..b1875a2bc6fb1 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/InspectionSchemeFactoryTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/InspectionSchemeFactoryTest.groovy @@ -19,6 +19,7 @@ package org.gradle.api.internal.tasks.properties import org.gradle.api.internal.tasks.properties.annotations.PropertyAnnotationHandler import org.gradle.cache.internal.TestCrossBuildInMemoryCacheFactory +import org.gradle.internal.instantiation.InstantiationScheme import spock.lang.Specification import java.lang.annotation.Retention @@ -27,10 +28,12 @@ import java.lang.annotation.RetentionPolicy class InspectionSchemeFactoryTest extends Specification { def handler1 = handler(Thing1) def handler2 = handler(Thing2) - def factory = new InspectionSchemeFactory([handler1, handler2], new TestCrossBuildInMemoryCacheFactory()) + def factory = new InspectionSchemeFactory([handler1, handler2], [], new TestCrossBuildInMemoryCacheFactory()) - def "creates inspection scheme"() { - def scheme = factory.inspectionScheme([Thing1, Thing2]) + def "creates inspection scheme that understands given property annotations and injection annotations"() { + def instantiationScheme = Stub(InstantiationScheme) + instantiationScheme.injectionAnnotations >> [InjectThing] + def scheme = factory.inspectionScheme([Thing1, Thing2], instantiationScheme) expect: def metadata = scheme.metadataStore.getTypeMetadata(AnnotatedBean) @@ -38,18 +41,32 @@ class InspectionSchemeFactoryTest extends Specification { metadata.collectValidationFailures(null, new DefaultParameterValidationContext(problems)) problems.empty metadata.propertiesMetadata.size() == 2 + + def properties = metadata.propertiesMetadata.groupBy { it.propertyName } + metadata.getAnnotationHandlerFor(properties.prop1) == handler1 + metadata.getAnnotationHandlerFor(properties.prop2) == handler2 } - def "reuses inspection schemes"() { - def scheme = factory.inspectionScheme([Thing1, Thing2]) + def "annotation can be used for property annotation and injection annotations"() { + def instantiationScheme = Stub(InstantiationScheme) + instantiationScheme.injectionAnnotations >> [Thing2, InjectThing] + def scheme = factory.inspectionScheme([Thing1, Thing2], instantiationScheme) expect: - factory.inspectionScheme([Thing2, Thing1]).is(scheme) - factory.inspectionScheme([Thing2]) != scheme + def metadata = scheme.metadataStore.getTypeMetadata(AnnotatedBean) + def problems = [] + metadata.collectValidationFailures(null, new DefaultParameterValidationContext(problems)) + problems.empty + metadata.propertiesMetadata.size() == 2 + + def properties = metadata.propertiesMetadata.groupBy { it.propertyName } + metadata.getAnnotationHandlerFor(properties.prop1) == handler1 + metadata.getAnnotationHandlerFor(properties.prop2) == handler2 } def handler(Class annotation) { def handler = Stub(PropertyAnnotationHandler) + _ * handler.propertyRelevant >> true _ * handler.annotationType >> annotation return handler } @@ -61,6 +78,9 @@ class AnnotatedBean { @Thing2 String prop2 + + @InjectThing + String prop3 } @Retention(RetentionPolicy.RUNTIME) @@ -70,3 +90,7 @@ class AnnotatedBean { @Retention(RetentionPolicy.RUNTIME) @interface Thing2 { } + +@Retention(RetentionPolicy.RUNTIME) +@interface InjectThing { +} diff --git a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/annotations/NestedBeanAnnotationHandlerTest.groovy b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/annotations/NestedBeanAnnotationHandlerTest.groovy index 6feef7a5462ad..9868c397638f9 100644 --- a/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/annotations/NestedBeanAnnotationHandlerTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/api/internal/tasks/properties/annotations/NestedBeanAnnotationHandlerTest.groovy @@ -58,7 +58,7 @@ class NestedBeanAnnotationHandlerTest extends Specification { validatingSpec.validate(validationContext) then: - 1 * validationContext.recordValidationMessage("No value has been specified for property 'name'.") + 1 * validationContext.visitError("No value has been specified for property 'name'.") 0 * _ } diff --git a/subprojects/core/src/test/groovy/org/gradle/execution/plan/DefaultExecutionPlanParallelTest.groovy b/subprojects/core/src/test/groovy/org/gradle/execution/plan/DefaultExecutionPlanParallelTest.groovy index 278e34f733dbb..8f146dc4022b6 100644 --- a/subprojects/core/src/test/groovy/org/gradle/execution/plan/DefaultExecutionPlanParallelTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/execution/plan/DefaultExecutionPlanParallelTest.groovy @@ -673,6 +673,88 @@ class DefaultExecutionPlanParallelTest extends AbstractProjectBuilderSpec { selectNextTask() == null } + def "must run after is sometimes not respected for finalizers"() { + Task dependency = project.task("dependency", type: Async) + Task finalized = project.task("finalized", type: Async) + Task finalizer = project.task("finalizer", type: Async) + Task mustRunAfter = project.task("mustRunAfter", type: Async) + + finalized.dependsOn(dependency) + finalized.finalizedBy(finalizer) + mustRunAfter.mustRunAfter(finalizer) + + when: + executionPlan.addEntryTasks([finalized]) + executionPlan.addEntryTasks([mustRunAfter]) + executionPlan.determineExecutionPlan() + + and: + def dependencyNode = selectNextTaskNode() + def mustRunAfterNode = selectNextTaskNode() + then: + selectNextTaskNode() == null + dependencyNode.task == dependency + mustRunAfterNode.task == mustRunAfter + + when: + executionPlan.nodeComplete(dependencyNode) + + def finalizedNode = selectNextTaskNode() + then: + selectNextTaskNode() == null + finalizedNode.task == finalized + + when: + executionPlan.nodeComplete(finalizedNode) + + def finalizerNode = selectNextTaskNode() + then: + selectNextTaskNode() == null + finalizerNode.task == finalizer + } + + def "must run after is sometimes respected for finalizers"() { + Task dependency = project.task("dependency", type: Async) + Task finalized = project.task("finalized", type: Async) + Task finalizer = project.task("finalizer", type: Async) + Task mustRunAfter = project.task("mustRunAfter", type: Async) + + finalized.dependsOn(dependency) + finalized.finalizedBy(finalizer) + mustRunAfter.mustRunAfter(finalizer) + + when: + executionPlan.addEntryTasks([finalized]) + executionPlan.addEntryTasks([mustRunAfter]) + executionPlan.determineExecutionPlan() + + and: + def dependencyNode = selectNextTaskNode() + then: + dependencyNode.task == dependency + + when: + executionPlan.nodeComplete(dependencyNode) + + def finalizedNode = selectNextTaskNode() + then: + finalizedNode.task == finalized + + when: + executionPlan.nodeComplete(finalizedNode) + + def finalizerNode = selectNextTaskNode() + then: + selectNextTaskNode() == null + finalizerNode.task == finalizer + + when: + executionPlan.nodeComplete(finalizerNode) + then: + selectNextTask() == mustRunAfter + selectNextTask() == null + } + def "handles an exception while walking the task graph when an enforced task is present"() { given: Task finalizer = project.task("finalizer", type: BrokenTask) diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy index 2536a9950e94c..5c3123d534abf 100644 --- a/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/initialization/DefaultSettingsTest.groovy @@ -29,6 +29,7 @@ import org.gradle.api.internal.plugins.DefaultPluginManager import org.gradle.configuration.ScriptPluginFactory import org.gradle.groovy.scripts.ScriptSource import org.gradle.integtests.fixtures.FeaturePreviewsFixture +import org.gradle.internal.instantiation.InstantiatorFactory import org.gradle.internal.service.ServiceRegistry import org.gradle.internal.service.scopes.ServiceRegistryFactory import org.gradle.util.TestUtil @@ -62,6 +63,7 @@ class DefaultSettingsTest extends Specification { settingsServices.get(ProjectDescriptorRegistry) >> projectDescriptorRegistry settingsServices.get(FeaturePreviews) >> previews settingsServices.get(DefaultPluginManager) >>> [pluginManager, null] + settingsServices.get(InstantiatorFactory) >> Stub(InstantiatorFactory) serviceRegistryFactory = Mock(ServiceRegistryFactory) { 1 * createFor(_) >> settingsServices diff --git a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.groovy b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.groovy index 8a404afa6efba..6544105e174c1 100644 --- a/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/initialization/SettingsFactoryTest.groovy @@ -26,6 +26,7 @@ import org.gradle.api.internal.initialization.ScriptHandlerInternal import org.gradle.api.internal.initialization.loadercache.DummyClassLoaderCache import org.gradle.configuration.ScriptPluginFactory import org.gradle.groovy.scripts.ScriptSource +import org.gradle.internal.instantiation.InstantiatorFactory import org.gradle.internal.service.ServiceRegistry import org.gradle.internal.service.scopes.ServiceRegistryFactory import org.gradle.util.TestUtil @@ -52,6 +53,7 @@ class SettingsFactoryTest extends Specification { 1 * settingsServices.get(ScriptPluginFactory) >> scriptPluginFactory 1 * settingsServices.get(ScriptHandlerFactory) >> scriptHandlerFactory 1 * settingsServices.get(ProjectDescriptorRegistry) >> projectDescriptorRegistry + 1 * settingsServices.get(InstantiatorFactory) >> Stub(InstantiatorFactory) 1 * projectDescriptorRegistry.addProject(_ as DefaultProjectDescriptor) 1 * scriptHandlerFactory.create(scriptSource, _ as ClassLoaderScope) >> Mock(ScriptHandlerInternal) diff --git a/subprojects/core/src/test/groovy/org/gradle/internal/fingerprint/classpath/impl/ClasspathFingerprintCompareStrategyTest.groovy b/subprojects/core/src/test/groovy/org/gradle/internal/fingerprint/classpath/impl/ClasspathFingerprintCompareStrategyTest.groovy index d524ee5890881..aef5292b99e92 100644 --- a/subprojects/core/src/test/groovy/org/gradle/internal/fingerprint/classpath/impl/ClasspathFingerprintCompareStrategyTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/internal/fingerprint/classpath/impl/ClasspathFingerprintCompareStrategyTest.groovy @@ -16,8 +16,9 @@ package org.gradle.internal.fingerprint.classpath.impl +import com.google.common.collect.Iterables import org.gradle.internal.change.CollectingChangeVisitor -import org.gradle.internal.change.FileChange +import org.gradle.internal.change.DefaultFileChange import org.gradle.internal.file.FileType import org.gradle.internal.fingerprint.FileSystemLocationFingerprint import org.gradle.internal.fingerprint.FingerprintCompareStrategy @@ -54,7 +55,7 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - CLASSPATH | true | [added("one-new")] + CLASSPATH | true | [added("one-new": "one")] CLASSPATH | false | [] } @@ -68,7 +69,7 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - CLASSPATH | true | [added("two-new")] + CLASSPATH | true | [added("two-new": "two")] CLASSPATH | false | [] } @@ -78,7 +79,7 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { changes(strategy, includeAdded, [:], ["one-old": fingerprint("one")] - ) as List == [removed("one-old")] + ) as List == [removed("one-old": "one")] where: strategy | includeAdded @@ -92,7 +93,7 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { changes(strategy, includeAdded, ["one-new": fingerprint("one")], ["one-old": fingerprint("one"), "two-old": fingerprint("two")] - ) == [removed("two-old")] + ) == [removed("two-old": "two")] where: strategy | includeAdded @@ -106,7 +107,7 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { changes(strategy, includeAdded, ["one-new": fingerprint("one"), "two-new": fingerprint("two", 0x9876cafe)], ["one-old": fingerprint("one"), "two-old": fingerprint("two", 0xface1234)] - ) == [modified("two-new", FileType.RegularFile, FileType.RegularFile)] + ) == [modified("two-new": "two", FileType.RegularFile, FileType.RegularFile)] where: strategy | includeAdded @@ -124,8 +125,8 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - CLASSPATH | true | [removed("one-old"), added("two-new")] - CLASSPATH | false | [removed("one-old")] + CLASSPATH | true | [removed("one-old": "one"), added("two-new": "two")] + CLASSPATH | false | [removed("one-old": "one")] } @Unroll @@ -138,8 +139,8 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - CLASSPATH | true | [removed("three-old"), added("two-new")] - CLASSPATH | false | [removed("three-old")] + CLASSPATH | true | [removed("three-old": "three"), added("two-new": "two")] + CLASSPATH | false | [removed("three-old": "three")] } @Unroll @@ -152,8 +153,8 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - CLASSPATH | true | [removed("three-old"), added("two-new"), removed("two-old"), added("three-new")] - CLASSPATH | false | [removed("three-old"), removed("two-old")] + CLASSPATH | true | [removed("three-old": "three"), added("two-new": "two"), removed("two-old": "two"), added("three-new": "three")] + CLASSPATH | false | [removed("three-old": "three"), removed("two-old": "two")] } @Unroll @@ -236,12 +237,6 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { visitor.getChanges().toList() } - def changesUsingAbsolutePaths(FingerprintCompareStrategy strategy, boolean includeAdded, Map current, Map previous) { - def visitor = new CollectingChangeVisitor() - strategy.visitChangesSince(visitor, current, previous, "test", includeAdded) - visitor.getChanges().toList() - } - def fingerprint(String normalizedPath, def hashCode = 0x1234abcd) { return new DefaultFileSystemLocationFingerprint(normalizedPath, FileType.RegularFile, HashCode.fromInt((int) hashCode)) } @@ -251,14 +246,29 @@ class ClasspathFingerprintCompareStrategyTest extends Specification { } def added(String path) { - FileChange.added(path, "test", FileType.RegularFile) + added((path): "") + } + + def added(Map entry) { + def singleEntry = Iterables.getOnlyElement(entry.entrySet()) + DefaultFileChange.added(singleEntry.key, "test", FileType.RegularFile, singleEntry.value) } def removed(String path) { - FileChange.removed(path, "test", FileType.RegularFile) + removed((path): "") + } + + def removed(Map entry) { + def singleEntry = Iterables.getOnlyElement(entry.entrySet()) + DefaultFileChange.removed(singleEntry.key, "test", FileType.RegularFile, singleEntry.value) } def modified(String path, FileType previous = FileType.RegularFile, FileType current = FileType.RegularFile) { - FileChange.modified(path, "test", previous, current) + modified((path): "", previous, current) + } + + def modified(Map paths, FileType previous = FileType.RegularFile, FileType current = FileType.RegularFile) { + def singleEntry = Iterables.getOnlyElement(paths.entrySet()) + DefaultFileChange.modified(singleEntry.key, "test", previous, current, singleEntry.value) } } diff --git a/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy b/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy index fde428621e47c..a7574b9043b23 100644 --- a/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy +++ b/subprojects/core/src/test/groovy/org/gradle/process/internal/JavaExecHandleBuilderTest.groovy @@ -15,9 +15,12 @@ */ package org.gradle.process.internal +import org.gradle.api.internal.file.DefaultFileCollectionFactory +import org.gradle.api.internal.file.FileCollectionFactory import org.gradle.api.internal.file.TestFiles import org.gradle.initialization.DefaultBuildCancellationToken import org.gradle.internal.jvm.Jvm +import spock.lang.Issue import spock.lang.Specification import spock.lang.Unroll @@ -29,7 +32,9 @@ import static java.util.Arrays.asList class JavaExecHandleBuilderTest extends Specification { JavaExecHandleBuilder builder = new JavaExecHandleBuilder(TestFiles.resolver(), TestFiles.fileCollectionFactory(), Mock(Executor), new DefaultBuildCancellationToken()) - public void cannotSetAllJvmArgs() { + FileCollectionFactory fileCollectionFactory = new DefaultFileCollectionFactory() + + def cannotSetAllJvmArgs() { when: builder.setAllJvmArgs(asList("arg")) @@ -38,7 +43,7 @@ class JavaExecHandleBuilderTest extends Specification { } @Unroll("buildsCommandLineForJavaProcess - input encoding #inputEncoding") - public void buildsCommandLineForJavaProcess() { + def buildsCommandLineForJavaProcess() { File jar1 = new File("file1.jar").canonicalFile File jar2 = new File("file2.jar").canonicalFile @@ -70,6 +75,52 @@ class JavaExecHandleBuilderTest extends Specification { "UTF-16" | "UTF-16" } + def "can append to classpath"() { + given: + File jar1 = new File("file1.jar").canonicalFile + File jar2 = new File("file2.jar").canonicalFile + + builder.classpath(jar1) + + when: + builder.classpath(jar2) + + then: + builder.classpath.contains(jar1) + builder.classpath.contains(jar2) + } + + def "can replace classpath"() { + given: + File jar1 = new File("file1.jar").canonicalFile + File jar2 = new File("file2.jar").canonicalFile + + builder.classpath(jar1) + + when: + builder.setClasspath(fileCollectionFactory.resolving(jar2)) + + then: + !builder.classpath.contains(jar1) + builder.classpath.contains(jar2) + } + + @Issue("gradle/gradle#8748") + def "can prepend to classpath"() { + given: + File jar1 = new File("file1.jar").canonicalFile + File jar2 = new File("file2.jar").canonicalFile + + builder.classpath(jar1) + + when: + builder.setClasspath(fileCollectionFactory.resolving(jar2, builder.getClasspath())) + + then: + builder.commandLine.contains("$jar2$File.pathSeparator$jar1".toString()) + + } + def "detects null entries early"() { when: builder.args(1, null) then: thrown(IllegalArgumentException) diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/test/fixtures/TestDeploymentFixture.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/test/fixtures/TestDeploymentFixture.groovy new file mode 100644 index 0000000000000..d38a51509560c --- /dev/null +++ b/subprojects/core/src/testFixtures/groovy/org/gradle/test/fixtures/TestDeploymentFixture.groovy @@ -0,0 +1,120 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.test.fixtures + +import org.gradle.deployment.internal.Deployment +import org.gradle.deployment.internal.DeploymentHandle +import org.gradle.deployment.internal.DeploymentRegistry +import org.gradle.test.fixtures.file.TestFile + +class TestDeploymentFixture { + private File triggerFile + private File keyFile + + void writeToProject(TestFile projectDir) { + triggerFile = projectDir.file('triggerFile') + keyFile = projectDir.file('keyFile') + + triggerFile.text = "0" + + projectDir.file("build.gradle") << """ + import javax.inject.Inject + import ${Deployment.canonicalName} + import ${DeploymentHandle.canonicalName} + import ${DeploymentRegistry.canonicalName} + import ${DeploymentRegistry.ChangeBehavior.canonicalName} + + task runDeployment(type: RunTestDeployment) { + triggerFile = file('${triggerFile.name}') + keyFile = file('${keyFile.name}') + } + + class TestDeploymentHandle implements DeploymentHandle { + final File keyFile + boolean running + + @Inject + TestDeploymentHandle(key, File keyFile) { + this.keyFile = keyFile + keyFile.text = key + } + + public void start(Deployment deployment) { + running = true + } + + public boolean isRunning() { + return running + } + + public void stop() { + running = false + keyFile.delete() + } + } + + class RunTestDeployment extends DefaultTask { + @InputFile + File triggerFile + + @OutputFile + File keyFile + + @Inject + protected DeploymentRegistry getDeploymentRegistry() { + throw new UnsupportedOperationException() + } + + @TaskAction + void doDeployment() { + // we set a different key for every build so we can detect if we + // somehow get a different deployment handle between builds + def key = System.currentTimeMillis() + + TestDeploymentHandle handle = getDeploymentRegistry().get('test', TestDeploymentHandle.class) + if (handle == null) { + // This should only happen once (1st build), so if we get a different value in keyFile between + // builds then we know we can detect if we didn't get the same handle + handle = getDeploymentRegistry().start('test', DeploymentRegistry.ChangeBehavior.NONE, TestDeploymentHandle.class, key, keyFile) + } + + println "\\nCurrent Key: \$key, Deployed Key: \$handle.keyFile.text" + assert handle.isRunning() + } + } + """ + } + + File getTriggerFile() { + return triggerFile + } + + File getKeyFile() { + return keyFile + } + + void assertDeploymentIsRunning(String key) { + // assert that the keyFile still exists and has the same contents (ie handle is still running) + assert keyFile.exists() + assert keyFile.text == key + } + + void assertDeploymentIsStopped() { + // assert that the deployment handle was stopped and removed the key file + assert !keyFile.exists() + } +} diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/SnapshotTestUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/SnapshotTestUtil.groovy index 392d50be81e49..20177b36ac85e 100644 --- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/SnapshotTestUtil.groovy +++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/SnapshotTestUtil.groovy @@ -16,7 +16,7 @@ package org.gradle.util -import org.gradle.api.internal.model.NamedObjectInstantiator + import org.gradle.internal.classloader.ClassLoaderHierarchyHasher import org.gradle.internal.hash.HashCode import org.gradle.internal.snapshot.ValueSnapshotter @@ -29,6 +29,6 @@ class SnapshotTestUtil { HashCode getClassLoaderHash(ClassLoader classLoader) { return HashCode.fromInt(classLoader.hashCode()) } - }, NamedObjectInstantiator.INSTANCE) + }) } } diff --git a/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestUtil.groovy b/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestUtil.groovy index c8b795ca349b0..1f31a911473ac 100644 --- a/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestUtil.groovy +++ b/subprojects/core/src/testFixtures/groovy/org/gradle/util/TestUtil.groovy @@ -72,10 +72,10 @@ class TestUtil { private static ObjectFactory objFactory(FileResolver fileResolver) { DefaultServiceRegistry services = new DefaultServiceRegistry() services.add(ProviderFactory, new DefaultProviderFactory()) + services.add(InstantiatorFactory, instantiatorFactory()) return new DefaultObjectFactory(instantiatorFactory().injectAndDecorate(services), NamedObjectInstantiator.INSTANCE, fileResolver, TestFiles.directoryFileTreeFactory(), new DefaultFilePropertyFactory(fileResolver), TestFiles.fileCollectionFactory()) } - static NamedObjectInstantiator objectInstantiator() { return NamedObjectInstantiator.INSTANCE } diff --git a/subprojects/dependency-management/dependency-management.gradle.kts b/subprojects/dependency-management/dependency-management.gradle.kts index 00f539a55dbb0..830c30039c94e 100644 --- a/subprojects/dependency-management/dependency-management.gradle.kts +++ b/subprojects/dependency-management/dependency-management.gradle.kts @@ -14,7 +14,6 @@ * limitations under the License. */ -import org.gradle.build.ClasspathManifest import org.gradle.gradlebuild.testing.integrationtests.cleanup.WhenNotEmpty import org.gradle.gradlebuild.unittestandcompile.ModuleType @@ -38,7 +37,6 @@ dependencies { implementation(library("ivy")) implementation(library("slf4j_api")) implementation(library("gson")) - implementation(library("jcip")) implementation(library("maven3")) runtimeOnly(library("bouncycastle_provider")) @@ -56,6 +54,8 @@ dependencies { testFixturesApi(project(":resourcesHttp", "testFixturesApiElements")) testFixturesImplementation(project(":internalIntegTesting")) + + crossVersionTestRuntimeOnly(project(":maven")) } gradlebuildJava { diff --git a/subprojects/dependency-management/src/crossVersionTest/groovy/org/gradle/integtests/resolve/GradleMetadataJavaLibraryCrossVersionIntegrationTest.groovy b/subprojects/dependency-management/src/crossVersionTest/groovy/org/gradle/integtests/resolve/GradleMetadataJavaLibraryCrossVersionIntegrationTest.groovy new file mode 100644 index 0000000000000..b15c0627218b7 --- /dev/null +++ b/subprojects/dependency-management/src/crossVersionTest/groovy/org/gradle/integtests/resolve/GradleMetadataJavaLibraryCrossVersionIntegrationTest.groovy @@ -0,0 +1,116 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.resolve + +import org.gradle.integtests.fixtures.CrossVersionIntegrationSpec +import org.gradle.integtests.fixtures.TargetVersions +import org.gradle.util.GradleVersion + +import static org.gradle.integtests.fixtures.AbstractIntegrationSpec.jcenterRepository + +@TargetVersions("5.2.1+") +class GradleMetadataJavaLibraryCrossVersionIntegrationTest extends CrossVersionIntegrationSpec { + + // The version in which Gradle metadata became "stable" + private static final GradleVersion STABLE_METADATA_VERSION = GradleVersion.version("5.3") + + def setup() { + settingsFile << """ + rootProject.name = 'test' + enableFeaturePreview('GRADLE_METADATA') + include 'consumer' + include 'producer' + """ + buildFile << """ + allprojects { + apply plugin: 'java-library' + + group = 'com.acme' + version = '1.0' + + repositories { + maven { url "\${rootProject.buildDir}/repo" } + ${jcenterRepository()} + } + } + """ + + file('producer/build.gradle') << """ + apply plugin: 'maven-publish' + + dependencies { + constraints { + api 'org.apache.commons:commons-lang3:3.8.1' + } + implementation('org.apache.commons:commons-lang3') { + version { + strictly '[3.8, 3.9[' + because "Doesn't work with other versions than 3.8" + } + } + } + + java { + if (JavaPluginExtension.metaClass.respondsTo(delegate, 'registerFeature')) { + registerFeature("hibernateSupport") { + usingSourceSet(sourceSets.main) + capability("com.acme", "producer-hibernate-support", "1.0") + } + } + } + + publishing { + repositories { + maven { url "\${rootProject.buildDir}/repo" } + } + + publications { + producerLib(MavenPublication) { + from components.java + } + } + } + """ + + file('consumer/build.gradle') << """ + dependencies { + api 'com.acme:producer:1.0' + } + + task resolve { + doLast { + println configurations.runtimeClasspath.files + } + } + """ + } + + def "can consume library published with previous version of Gradle"() { + expect: + version previous withTasks ':producer:publish' run() + version current withTasks ':consumer:resolve' run() + } + + def "previous Gradle can consume library published with current version of Gradle"() { + expect: + version current withTasks ':producer:publish' run() + if (previous.version.compareTo(STABLE_METADATA_VERSION) >= 0) { + version previous withTasks ':consumer:resolve' run() + } + } + +} diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/api/artifacts/transform/ArtifactTransformBuildScanIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/api/artifacts/transform/ArtifactTransformBuildScanIntegrationTest.groovy index 9ef45f587d4b0..c761e9501da5d 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/api/artifacts/transform/ArtifactTransformBuildScanIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/api/artifacts/transform/ArtifactTransformBuildScanIntegrationTest.groovy @@ -17,13 +17,8 @@ package org.gradle.api.artifacts.transform import org.gradle.integtests.fixtures.AbstractIntegrationSpec -import org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner import org.gradle.internal.scan.config.fixtures.BuildScanPluginFixture -import org.junit.runner.RunWith -import static org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner.configureIncrementalArtifactTransformations - -@RunWith(ExperimentalIncrementalArtifactTransformationsRunner) class ArtifactTransformBuildScanIntegrationTest extends AbstractIntegrationSpec { def fixture = new BuildScanPluginFixture(testDirectory, mavenRepo, createExecuter()) @@ -32,7 +27,6 @@ class ArtifactTransformBuildScanIntegrationTest extends AbstractIntegrationSpec include 'lib' include 'util' """ - configureIncrementalArtifactTransformations(settingsFile) fixture.logConfig = true fixture.publishDummyBuildScanPluginNow() } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy index 4cb058ed222e9..a25899434a229 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/DependencySubstitutionRulesIntegrationTest.groovy @@ -1052,7 +1052,7 @@ class DependencySubstitutionRulesIntegrationTest extends AbstractIntegrationSpec fails "checkDeps" then: - failure.assertHasCause("Could not resolve all task dependencies for configuration ':conf'.") + failure.assertHasCause("Could not resolve all dependencies for configuration ':conf'.") failure.assertHasCause("Could not find org.utils:api:1.123.15") } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/GradlePluginPortalDependencyResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/GradlePluginPortalDependencyResolveIntegrationTest.groovy index f8d07ed9adae3..48a95cff4fed8 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/GradlePluginPortalDependencyResolveIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/GradlePluginPortalDependencyResolveIntegrationTest.groovy @@ -22,7 +22,17 @@ import org.gradle.util.TestPrecondition @Requires(TestPrecondition.ONLINE) class GradlePluginPortalDependencyResolveIntegrationTest extends AbstractDependencyResolutionTest { - def gradlePluginPortalRepository = "repositories { gradlePluginPortal() }" + def gradlePluginPortalRepository = """ + repositories { + gradlePluginPortal() + gradlePluginPortal { // just test this syntax works. + name = "otherPluginPortal" + content { + includeGroup 'org.sample' + } + } + } + """ def pluginClasspathDependency = "org.gradle:gradle-hello-world-plugin:0.2" def "buildscript dependencies can be resolved from gradlePluginPortal()"() { diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy index 8e2380c4e1538..a4fabfa72e559 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/ProjectDependencyResolveIntegrationTest.groovy @@ -187,7 +187,7 @@ project(":b") { variant('runtime') module('org.other:externalA:1.2') { byReason('also check dependency reasons') - variant('runtime', ['org.gradle.status': 'release', 'org.gradle.component.category':'library', 'org.gradle.usage':'java-runtime']) + variant('runtime', ['org.gradle.status': 'release', 'org.gradle.category':'library', 'org.gradle.usage':'java-runtime']) } } } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RemoteDependencyResolveConsoleIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RemoteDependencyResolveConsoleIntegrationTest.groovy index 1339cefa225b8..f4f51c2a6141b 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RemoteDependencyResolveConsoleIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/RemoteDependencyResolveConsoleIntegrationTest.groovy @@ -20,6 +20,7 @@ import org.gradle.api.logging.configuration.ConsoleOutput import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest import org.gradle.integtests.fixtures.RichConsoleStyling import org.gradle.integtests.fixtures.executer.GradleHandle +import org.gradle.integtests.fixtures.executer.LogContent import org.gradle.test.fixtures.ConcurrentTestUtil import org.gradle.test.fixtures.server.http.BlockingHttpServer import org.junit.Rule @@ -119,12 +120,13 @@ class RemoteDependencyResolveConsoleIntegrationTest extends AbstractDependencyRe } void outputContainsProgress(GradleHandle build, String taskProgressLine, String... progressOutputLines) { - assert build.standardOutput.contains(workInProgressLine(taskProgressLine)) || - progressOutputLines.any { build.standardOutput.contains(workInProgressLine(taskProgressLine + " " + it)) } + def output = LogContent.of(build.standardOutput).ansiCharsToColorText().withNormalizedEol() + assert output.contains(workInProgressLine(taskProgressLine)) || + progressOutputLines.any { output.contains(workInProgressLine(taskProgressLine + " " + it)) } assert progressOutputLines.every { - build.standardOutput.contains(workInProgressLine(it)) || - build.standardOutput.contains(workInProgressLine(taskProgressLine + " " + it)) + output.contains(workInProgressLine(it)) || + output.contains(workInProgressLine(taskProgressLine + " " + it)) } } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/alignment/ForcingPlatformAlignmentTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/alignment/ForcingPlatformAlignmentTest.groovy index ecf2435e6ea89..c6ec2565d9ec0 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/alignment/ForcingPlatformAlignmentTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/alignment/ForcingPlatformAlignmentTest.groovy @@ -1063,10 +1063,11 @@ include 'other' module(platformName) { version(platformVersion) { variant("platform") { - attribute('org.gradle.component.category', 'platform') + attribute('org.gradle.category', 'platform') members.each { member -> constraint(member) } + noArtifacts = true } // this is used only in BOMs members.each { member -> diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/attributes/CrossProjectMultipleVariantSelectionIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/attributes/CrossProjectMultipleVariantSelectionIntegrationTest.groovy index 4ffd3e899420e..10486d5c6944c 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/attributes/CrossProjectMultipleVariantSelectionIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/attributes/CrossProjectMultipleVariantSelectionIntegrationTest.groovy @@ -16,6 +16,7 @@ package org.gradle.integtests.resolve.attributes +import org.gradle.api.JavaVersion import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest import org.gradle.integtests.fixtures.resolve.ResolveTestFixture @@ -48,6 +49,7 @@ class CrossProjectMultipleVariantSelectionIntegrationTest extends AbstractDepend attributes { attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage, 'java-api-jars')) attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling, Bundling.EXTERNAL)) + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, Integer.valueOf(JavaVersion.current().majorVersion)) } outgoing.capability('org:lib-fixtures:1.0') } @@ -79,11 +81,11 @@ class CrossProjectMultipleVariantSelectionIntegrationTest extends AbstractDepend resolve.expectGraph { root(":", ":test:") { project(":lib", "test:lib:") { - variant "apiElements", ['org.gradle.usage':'java-api-jars', 'org.gradle.dependency.bundling':'external'] + variant "apiElements", ['org.gradle.usage':'java-api-jars', 'org.gradle.category':'library', 'org.gradle.dependency.bundling':'external', 'org.gradle.jvm.version': JavaVersion.current().majorVersion] artifact group:'', module:'', version: '', type: '', name: 'main', noType: true } project(":lib", "test:lib:") { - variant "testFixtures", ['org.gradle.usage':'java-api-jars', 'org.gradle.dependency.bundling':'external'] + variant "testFixtures", ['org.gradle.usage':'java-api-jars', 'org.gradle.dependency.bundling':'external', 'org.gradle.jvm.version': JavaVersion.current().majorVersion] artifact group:'test', module:'lib', version:'unspecified', classifier: 'test-fixtures' } } @@ -101,7 +103,9 @@ class CrossProjectMultipleVariantSelectionIntegrationTest extends AbstractDepend canBeConsumed = true attributes { attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage, 'java-api-jars')) + attribute(Category.CATEGORY_ATTRIBUTE, project.objects.named(Category, Category.LIBRARY)) attribute(Bundling.BUNDLING_ATTRIBUTE, project.objects.named(Bundling, Bundling.EXTERNAL)) + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, Integer.valueOf(JavaVersion.current().majorVersion)) } outgoing.capability('test:lib:1.0') outgoing.capability('test:lib-fixtures:1.0') @@ -126,7 +130,7 @@ class CrossProjectMultipleVariantSelectionIntegrationTest extends AbstractDepend resolve.expectGraph { root(":", ":test:") { project(":lib", "test:lib:") { - variant "apiElements", ['org.gradle.usage':'java-api-jars', 'org.gradle.dependency.bundling':'external'] + variant "apiElements", ['org.gradle.usage':'java-api-jars', 'org.gradle.category':'library', 'org.gradle.dependency.bundling':'external', 'org.gradle.jvm.version': JavaVersion.current().majorVersion] artifact group:'', module:'', version: '', type: '', name: 'main', noType: true } } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/ClassifierToVariantResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/ClassifierToVariantResolveIntegrationTest.groovy new file mode 100644 index 0000000000000..263160233eab4 --- /dev/null +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/ClassifierToVariantResolveIntegrationTest.groovy @@ -0,0 +1,147 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.resolve.maven + +import org.gradle.api.attributes.Usage +import org.gradle.integtests.fixtures.AbstractHttpDependencyResolutionTest +import org.gradle.integtests.fixtures.resolve.ResolveTestFixture + +class ClassifierToVariantResolveIntegrationTest extends AbstractHttpDependencyResolutionTest { + ResolveTestFixture resolve = new ResolveTestFixture(buildFile, "compileClasspath") + + def setup() { + settingsFile << """ + rootProject.name = 'test' + """ + buildFile << """ + apply plugin: 'java-library' + + repositories { + maven { url "${mavenHttpRepo.uri}" } + } + + """ + resolve.expectDefaultConfiguration('compile') + resolve.prepare() + } + + /** + * This simulates the case where a library is published with Gradle metadata, and + * that this library published additional variants, that use an artifact with a classifier. + * If a Maven consumer wants to use that artifact, it has no choice but using , + * so if a Gradle consumer depends on that Maven published library, we want to make sure we + * can match this classified dependency to a proper variant. + */ + def "reasonable behavior when a Maven library uses a classifier to select a Gradle variant"() { + def gradleLibrary = mavenHttpRepo.module("org", "lib", "1.0") + .adhocVariants() + .variant("apiElements", ['org.gradle.usage': Usage.JAVA_API_JARS, 'groovy.runtime': 'classic']) + .variant("runtimeElements", ['org.gradle.usage': Usage.JAVA_RUNTIME_JARS, 'groovy.runtime': 'classic']) + .variant("apiElementsIndy", ['org.gradle.usage': Usage.JAVA_API_JARS, 'groovy.runtime': 'indy']) { + artifact("lib-1.0-indy.jar") + } + .variant("runtimeElementsIndy", ['org.gradle.usage': Usage.JAVA_RUNTIME_JARS, 'groovy.runtime': 'indy']) { + artifact("lib-1.0-indy.jar") + } + .withGradleMetadataRedirection() + .withModuleMetadata() + .publish() + def mavenConsumer = mavenHttpRepo.module("org", "maven-consumer", "1.0") + .dependsOn("org", "lib", "1.0", "jar", "compile", "indy") + .publish() + + buildFile << """ + dependencies { + api 'org:maven-consumer:1.0' + } + """ + + when: + mavenConsumer.pom.expectGet() + mavenConsumer.artifact.expectGet() + gradleLibrary.pom.expectGet() + gradleLibrary.moduleMetadata.expectGet() + gradleLibrary.getArtifact(classifier: 'indy').expectGet() + run ':checkDeps' + + then: + resolve.expectGraph { + root(":", ":test:") { + module('org:maven-consumer:1.0') { + module('org:lib:1.0') { + variant('apiElementsIndy', [ + 'org.gradle.usage': Usage.JAVA_API_JARS, + 'org.gradle.status': 'release', + 'groovy.runtime': 'indy']) + artifact(name: 'lib', version: '1.0', classifier: 'indy') + } + } + } + } + + } + + /** + * A Gradle consumer should _not_ do this, but use attributes instead. However, + * there's nothing which prevents this from being done, so we must make sure it is + * supported. The path is exactly the same as when a Maven consumer wants to depend + * on a library published with Gradle that uses variants published using different + * classified artifacts. + */ + def "reasonable behavior when a Gradle consumer uses a classifier to select a Gradle variant"() { + def gradleLibrary = mavenHttpRepo.module("org", "lib", "1.0") + .adhocVariants() + .variant("apiElements", ['org.gradle.usage': Usage.JAVA_API_JARS, 'groovy.runtime': 'classic']) + .variant("runtimeElements", ['org.gradle.usage': Usage.JAVA_RUNTIME_JARS, 'groovy.runtime': 'classic']) + .variant("apiElementsIndy", ['org.gradle.usage': Usage.JAVA_API_JARS, 'groovy.runtime': 'indy']) { + artifact("lib-1.0-indy.jar") + } + .variant("runtimeElementsIndy", ['org.gradle.usage': Usage.JAVA_RUNTIME_JARS, 'groovy.runtime': 'indy']) { + artifact("lib-1.0-indy.jar") + } + .withGradleMetadataRedirection() + .withModuleMetadata() + .publish() + + buildFile << """ + dependencies { + api 'org:lib:1.0:indy' + } + """ + + when: + gradleLibrary.pom.expectGet() + gradleLibrary.moduleMetadata.expectGet() + gradleLibrary.getArtifact(classifier: 'indy').expectGet() + run ':checkDeps' + + then: + resolve.expectGraph { + root(":", ":test:") { + module('org:lib:1.0') { + variant('apiElementsIndy', [ + 'org.gradle.usage': Usage.JAVA_API_JARS, + 'org.gradle.status': 'release', + 'groovy.runtime': 'indy']) + artifact(name: 'lib', version: '1.0', classifier: 'indy') + } + } + } + + } + +} diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenBomResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenBomResolveIntegrationTest.groovy index e7d809cb6c877..127ed428f64a8 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenBomResolveIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenBomResolveIntegrationTest.groovy @@ -169,12 +169,12 @@ class MavenBomResolveIntegrationTest extends AbstractHttpDependencyResolutionTes resolve.expectGraph { root(':', ':testproject:') { module("group:bom:1.0") { - variant("platform-runtime", ['org.gradle.component.category':'platform', 'org.gradle.status':'release', 'org.gradle.usage':'java-runtime']) + variant("platform-runtime", ['org.gradle.category':'platform', 'org.gradle.status':'release', 'org.gradle.usage':'java-runtime']) constraint("group:moduleA:2.0") noArtifacts() } module("group:bom:1.0") { - variant("runtime", ['org.gradle.component.category':'library', 'org.gradle.status':'release', 'org.gradle.usage':'java-runtime']) + variant("runtime", ['org.gradle.category':'library', 'org.gradle.status':'release', 'org.gradle.usage':'java-runtime']) module("group:moduleC:1.0") noArtifacts() } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyWithGradleMetadataResolutionIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyWithGradleMetadataResolutionIntegrationTest.groovy index 066db41a4368d..3d165c4d22c9c 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyWithGradleMetadataResolutionIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalDependencyWithGradleMetadataResolutionIntegrationTest.groovy @@ -20,7 +20,7 @@ import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest import org.gradle.integtests.fixtures.FeaturePreviewsFixture import org.gradle.integtests.fixtures.resolve.ResolveTestFixture -import static org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser.FORMAT_VERSION +import static org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser.FORMAT_VERSION class MavenLocalDependencyWithGradleMetadataResolutionIntegrationTest extends AbstractDependencyResolutionTest { def resolve = new ResolveTestFixture(buildFile) diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy index d4e9075493ead..721261f79d618 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenLocalRepoResolveIntegrationTest.groovy @@ -27,7 +27,11 @@ class MavenLocalRepoResolveIntegrationTest extends AbstractDependencyResolutionT using m2 buildFile << """ repositories { - mavenLocal() + mavenLocal { + content { + excludeGroup 'unused' + } + } } configurations { compile } dependencies { diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyWithGradleMetadataResolutionIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyWithGradleMetadataResolutionIntegrationTest.groovy index 61486537a7d2d..f6b769290335b 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyWithGradleMetadataResolutionIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MavenRemoteDependencyWithGradleMetadataResolutionIntegrationTest.groovy @@ -21,7 +21,7 @@ import org.gradle.integtests.fixtures.FeaturePreviewsFixture import org.gradle.integtests.fixtures.resolve.ResolveTestFixture import spock.lang.Unroll -import static org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser.FORMAT_VERSION +import static org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser.FORMAT_VERSION class MavenRemoteDependencyWithGradleMetadataResolutionIntegrationTest extends AbstractHttpDependencyResolutionTest { def resolve = new ResolveTestFixture(buildFile).expectDefaultConfiguration("runtime") @@ -774,47 +774,6 @@ dependencies { failure.assertHasCause("Could not parse module metadata ${m.moduleMetadata.uri}") } - def "reports failure to accept module metadata with unexpected format version"() { - def m = mavenHttpRepo.module("test", "a", "1.2").withModuleMetadata().publish() - m.moduleMetadata.file.text = m.moduleMetadata.file.text.replace(FORMAT_VERSION, "123.67") - - given: - buildFile << """ -repositories { - maven { - url = '${mavenHttpRepo.uri}' - } -} -configurations { compile } -dependencies { - compile 'test:a:1.2' -} -""" - - m.moduleMetadata.expectGet() - - when: - fails("checkDeps") - - then: - failure.assertHasCause("Could not resolve all dependencies for configuration ':compile'.") - failure.assertHasCause("Could not resolve test:a:1.2.") - failure.assertHasCause("Could not parse module metadata ${m.moduleMetadata.uri}") - failure.assertHasCause("Unsupported format version '123.67' specified in module metadata. This version of Gradle supports format version ${FORMAT_VERSION} only.") - - when: - server.resetExpectations() - m.moduleMetadata.expectHead() - - fails("checkDeps") - - then: - failure.assertHasCause("Could not resolve all dependencies for configuration ':compile'.") - failure.assertHasCause("Could not resolve test:a:1.2.") - failure.assertHasCause("Could not parse module metadata ${m.moduleMetadata.uri}") - failure.assertHasCause("Unsupported format version '123.67' specified in module metadata. This version of Gradle supports format version ${FORMAT_VERSION} only.") - } - def "reports failure to locate files"() { def m = mavenHttpRepo.module("test", "a", "1.2").withModuleMetadata() m.artifact(classifier: 'extra') diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MixedMavenAndIvyModulesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MixedMavenAndIvyModulesIntegrationTest.groovy index 360c4ab1a41c2..8b0c584329c3a 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MixedMavenAndIvyModulesIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/maven/MixedMavenAndIvyModulesIntegrationTest.groovy @@ -206,14 +206,16 @@ dependencies { } } - def "selects correct configuration of ivy module when dependency from consuming maven module is substituted"() { - def notRequired = ivyRepo.module("org.test", "ignore-me", "1.0") + def "selects default configuration of ivy module when dependency from consuming maven module is substituted"() { def m1 = ivyRepo.module("org.test", "m1", "1.0") .configuration("compile") .publish() def m2 = ivyRepo.module("org.test", "m2", "1.0").publish() .configuration("master") .publish() + def m3 = ivyRepo.module("org.test", "m3", "1.0").publish() + .configuration("master") + .publish() ivyRepo.module("org.test", "ivy", "1.2") .configuration("compile") .configuration("runtime") @@ -222,10 +224,10 @@ dependencies { .configuration("default") .dependsOn(m1, conf: "compile") .dependsOn(m2, conf: "master") - .dependsOn(notRequired, conf: "*,!compile,!master->unknown") + .dependsOn(m3, conf: "*,!compile,!master") .artifact(name: "compile", conf: "compile") .artifact(name: "master", conf: "master") - .artifact(name: 'ignore-me', conf: "other,default,runtime") + .artifact(name: 'default', conf: "other,default,runtime") .publish() def ivyModule = ivyRepo.module("org.test", "ivy", "1.0") mavenRepo.module("org.test", "maven", "1.0") @@ -246,10 +248,8 @@ configurations.conf.resolutionStrategy.force('org.test:ivy:1.2') configuration = 'compile' edge('org.test:ivy:1.0', 'org.test:ivy:1.2') { forced() - artifact(name: 'compile') - artifact(name: 'master') - module('org.test:m1:1.0') - module('org.test:m2:1.0') + artifact(name: 'default') + module('org.test:m3:1.0') } } } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/platforms/JavaPlatformResolveIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/platforms/JavaPlatformResolveIntegrationTest.groovy index 0c8af79cedb9b..72a75a57d6deb 100755 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/platforms/JavaPlatformResolveIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/platforms/JavaPlatformResolveIntegrationTest.groovy @@ -16,8 +16,8 @@ package org.gradle.integtests.resolve.platforms +import org.gradle.api.attributes.Category import org.gradle.api.attributes.Usage -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport import org.gradle.integtests.fixtures.AbstractHttpDependencyResolutionTest import org.gradle.integtests.fixtures.FeaturePreviewsFixture import org.gradle.integtests.fixtures.resolve.ResolveTestFixture @@ -70,7 +70,7 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio resolve.expectGraph { root(":", "org.test:test:1.9") { project(":platform", "org.test:platform:1.9") { - variant("apiElements", ['org.gradle.usage': 'java-api', 'org.gradle.component.category': 'platform']) + variant("apiElements", ['org.gradle.usage': 'java-api', 'org.gradle.category': 'platform']) constraint("org:foo:1.1") noArtifacts() } @@ -114,7 +114,7 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio resolve.expectGraph { root(":", "org.test:test:1.9") { project(":platform", "org.test:platform:1.9") { - variant("runtimeElements", ['org.gradle.usage': 'java-runtime', 'org.gradle.component.category': 'platform']) + variant("runtimeElements", ['org.gradle.usage': 'java-runtime', 'org.gradle.category': 'platform']) constraint("org:foo:1.1") constraint("org:bar:1.2") noArtifacts() @@ -159,7 +159,7 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio resolve.expectGraph { root(":", "org.test:test:1.9") { project(":platform", "org.test:platform:1.9") { - variant("apiElements", ['org.gradle.usage': 'java-api', 'org.gradle.component.category': 'platform']) + variant("apiElements", ['org.gradle.usage': 'java-api', 'org.gradle.category': 'platform']) constraint("org:foo:1.1") noArtifacts() } @@ -200,7 +200,7 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio resolve.expectGraph { root(":", "org.test:test:1.9") { project(":platform", "org.test:platform:1.9") { - variant("enforcedApiElements", ['org.gradle.usage': 'java-api', 'org.gradle.component.category': 'enforced-platform']) + variant("enforcedApiElements", ['org.gradle.usage': 'java-api', 'org.gradle.category': 'enforced-platform']) constraint("org:foo:1.1") noArtifacts() } @@ -219,9 +219,9 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio def platform = mavenHttpRepo.module("org", "platform", "1.0") .withModuleMetadata() .adhocVariants() - .variant("apiElements", [(Usage.USAGE_ATTRIBUTE.name): Usage.JAVA_API, (PlatformSupport.COMPONENT_CATEGORY.name): PlatformSupport.REGULAR_PLATFORM]) { useDefaultArtifacts = false } + .variant("apiElements", [(Usage.USAGE_ATTRIBUTE.name): Usage.JAVA_API, (Category.CATEGORY_ATTRIBUTE.name): Category.REGULAR_PLATFORM]) { useDefaultArtifacts = false } .dependsOn("org", "foo", "1.0") - .variant("runtimeElements", [(Usage.USAGE_ATTRIBUTE.name): Usage.JAVA_RUNTIME, (PlatformSupport.COMPONENT_CATEGORY.name): PlatformSupport.REGULAR_PLATFORM]) { + .variant("runtimeElements", [(Usage.USAGE_ATTRIBUTE.name): Usage.JAVA_RUNTIME, (Category.CATEGORY_ATTRIBUTE.name): Category.REGULAR_PLATFORM]) { useDefaultArtifacts = false } .dependsOn("org", "foo", "1.0") @@ -254,7 +254,7 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio configuration = "enforcedApiElements" variant("enforcedApiElements", [ 'org.gradle.usage': 'java-api', - 'org.gradle.component.category': 'enforced-platform', + 'org.gradle.category': 'enforced-platform', 'org.gradle.status': 'release', ]) module("org:foo:1.0") @@ -294,7 +294,7 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio edge("org:platform", "org:platform:1.0") { variant("platform-compile", [ 'org.gradle.usage': 'java-api', - 'org.gradle.component.category': 'platform', + 'org.gradle.category': 'platform', 'org.gradle.status': 'release', ]) byConstraint() @@ -303,7 +303,7 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio constraint("org:platform:1.0", "org:platform:1.0") { variant("platform-compile", [ 'org.gradle.usage': 'java-api', - 'org.gradle.component.category': 'platform', + 'org.gradle.category': 'platform', 'org.gradle.status': 'release', ]) } @@ -335,7 +335,7 @@ class JavaPlatformResolveIntegrationTest extends AbstractHttpDependencyResolutio root(":", "org.test:test:1.9") { module("org:top:1.0") { variant("enforced-platform-compile", [ - 'org.gradle.component.category': 'enforced-platform', + 'org.gradle.category': 'enforced-platform', 'org.gradle.status': 'release', 'org.gradle.usage': 'java-api']) noArtifacts() diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/rules/ComponentReplacementIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/rules/ComponentReplacementIntegrationTest.groovy index 70c93ed78bdb4..a2bc1f484e1ca 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/rules/ComponentReplacementIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/rules/ComponentReplacementIntegrationTest.groovy @@ -429,9 +429,9 @@ class ComponentReplacementIntegrationTest extends AbstractIntegrationSpec { then: result.groupedOutput.task(':dependencyInsight').output.contains("""org:b:1 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Selected by rule : $expected diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/rules/VariantAttributesRulesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/rules/VariantAttributesRulesIntegrationTest.groovy index fc7ec98591901..fcddf2457898a 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/rules/VariantAttributesRulesIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/rules/VariantAttributesRulesIntegrationTest.groovy @@ -205,7 +205,7 @@ class VariantAttributesRulesIntegrationTest extends AbstractModuleDependencyReso // the format attribute is added by the rule expectedAttributes = [format: 'custom', 'org.gradle.status': GradleMetadataResolveRunner.useIvy() ? 'integration' : 'release'] expectedAttributes['org.gradle.usage'] = 'java-api' - expectedAttributes['org.gradle.component.category'] = 'library' + expectedAttributes['org.gradle.category'] = 'library' } } variant(expectedTargetVariant, expectedAttributes) diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/suppliers/DynamicRevisionRemoteResolveWithMetadataSupplierIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/suppliers/DynamicRevisionRemoteResolveWithMetadataSupplierIntegrationTest.groovy index b9de31e9bd478..c3618b9cc2c19 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/suppliers/DynamicRevisionRemoteResolveWithMetadataSupplierIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/suppliers/DynamicRevisionRemoteResolveWithMetadataSupplierIntegrationTest.groovy @@ -16,10 +16,10 @@ package org.gradle.integtests.resolve.suppliers import org.gradle.api.internal.artifacts.ivyservice.CacheLayout -import org.gradle.integtests.fixtures.cache.CachingIntegrationFixture import org.gradle.integtests.fixtures.GradleMetadataResolveRunner import org.gradle.integtests.fixtures.RequiredFeature import org.gradle.integtests.fixtures.RequiredFeatures +import org.gradle.integtests.fixtures.cache.CachingIntegrationFixture import org.gradle.integtests.resolve.AbstractModuleDependencyResolveTest import org.gradle.test.fixtures.HttpModule import org.gradle.test.fixtures.file.TestFile @@ -1040,8 +1040,8 @@ group:projectB:2.2;release then: noExceptionThrown() - result.assertRawOutputContains "Found result for rule [DefaultConfigurableRule{rule=class MP, ruleParams=[]}] and key group:projectB:2.2" - result.assertRawOutputContains "Found result for rule [DefaultConfigurableRule{rule=class MP, ruleParams=[]}] and key group:projectB:1.1" + outputContains "Found result for rule [DefaultConfigurableRule{rule=class MP, ruleParams=[]}] and key group:projectB:2.2" + outputContains "Found result for rule [DefaultConfigurableRule{rule=class MP, ruleParams=[]}] and key group:projectB:1.1" } def "changing the implementation of a rule invalidates the cache"() { diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformCachingIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformCachingIntegrationTest.groovy index 8c7ecaa3c742f..62cfb466241b8 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformCachingIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformCachingIntegrationTest.groovy @@ -20,25 +20,18 @@ import org.gradle.api.internal.artifacts.ivyservice.CacheLayout import org.gradle.api.internal.artifacts.transform.DefaultTransformationWorkspace import org.gradle.cache.internal.LeastRecentlyUsedCacheCleanup import org.gradle.integtests.fixtures.AbstractHttpDependencyResolutionTest -import org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner -import org.gradle.integtests.fixtures.RequiredFeature -import org.gradle.integtests.fixtures.RequiredFeatures import org.gradle.integtests.fixtures.cache.FileAccessTimeJournalFixture import org.gradle.test.fixtures.file.TestFile import org.gradle.test.fixtures.server.http.BlockingHttpServer import org.junit.Rule -import org.junit.runner.RunWith import spock.lang.Unroll import java.util.regex.Pattern import static java.util.concurrent.TimeUnit.MILLISECONDS import static java.util.concurrent.TimeUnit.SECONDS -import static org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner.configureIncrementalArtifactTransformations -import static org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner.incrementalArtifactTransformations import static org.gradle.test.fixtures.ConcurrentTestUtil.poll -@RunWith(ExperimentalIncrementalArtifactTransformationsRunner) class ArtifactTransformCachingIntegrationTest extends AbstractHttpDependencyResolutionTest implements FileAccessTimeJournalFixture { private final static long MAX_CACHE_AGE_IN_DAYS = LeastRecentlyUsedCacheCleanup.DEFAULT_MAX_AGE_IN_DAYS_FOR_RECREATABLE_CACHE_ENTRIES @@ -51,7 +44,6 @@ class ArtifactTransformCachingIntegrationTest extends AbstractHttpDependencyReso include 'util' include 'app' """ - configureIncrementalArtifactTransformations(settingsFile) buildFile << resolveTask } @@ -242,8 +234,7 @@ class ArtifactTransformCachingIntegrationTest extends AbstractHttpDependencyReso println "Transformed \$input.name to \$outSize.name into \$outputDirectory" outSize.text = String.valueOf(input.length()) return [outSize] - } - if (target.equals("hash")) { + } else if (target.equals("hash")) { def outHash = new File(outputDirectory, input.name + ".hash") println "Transformed \$input.name to \$outHash.name into \$outputDirectory" outHash.text = 'hash' @@ -702,11 +693,7 @@ class ArtifactTransformCachingIntegrationTest extends AbstractHttpDependencyReso output.count("Transformed") == 2 isTransformed("dir1.classes", "dir1.classes.dir") isTransformed("lib1.jar", "lib1.jar.txt") - if (incrementalArtifactTransformations) { - outputDir("dir1.classes", "dir1.classes.dir") == outputDir1 - } else { - outputDir("dir1.classes", "dir1.classes.dir") != outputDir1 - } + outputDir("dir1.classes", "dir1.classes.dir") == outputDir1 gradleUserHomeOutputDir("lib1.jar", "lib1.jar.txt") != outputDir2 when: @@ -718,7 +705,6 @@ class ArtifactTransformCachingIntegrationTest extends AbstractHttpDependencyReso output.count("Transformed") == 0 } - @RequiredFeatures(@RequiredFeature(feature = ExperimentalIncrementalArtifactTransformationsRunner.INCREMENTAL_ARTIFACT_TRANSFORMATIONS, value = "true")) def "transform is executed in different workspace for different file produced in chain"() { given: buildFile << declareAttributes() << withJarTasks() << duplicatorTransform << """ @@ -831,7 +817,7 @@ class ArtifactTransformCachingIntegrationTest extends AbstractHttpDependencyReso } @Unroll - def "failure in transformation chain propagates (position in chain: #failingTransform"() { + def "failure in transformation chain propagates (position in chain: #failingTransform)"() { given: Closure possiblyFailingTransform = { index -> @@ -1493,15 +1479,12 @@ ${getFileSizerBody(fileValue, 'new File(outputDirectory, ', 'new File(outputDire String registerFileSizerWithParameterObject(String fileValue) { """ - @AssociatedTransformAction(FileSizerAction) - interface FileSizer { - @Input - Number getValue() - void setValue(Number value) - } - abstract class FileSizerAction implements TransformAction { - @TransformParameters - abstract FileSizer getParameters() + abstract class FileSizer implements TransformAction { + interface Parameters extends TransformParameters { + @Input + Number getValue() + void setValue(Number value) + } @InputArtifact abstract File getInput() @@ -1662,12 +1645,8 @@ ${getFileSizerBody(fileValue, 'outputs.dir(', 'outputs.file(')} } Set projectOutputDirs(String from, String to, Closure stream = { output }) { - if (incrementalArtifactTransformations) { - def parts = [Pattern.quote(temporaryFolder.getTestDirectory().absolutePath) + ".*", "build", "transforms", "\\w+"] - return outputDirs(from, to, parts.join(quotedFileSeparator), stream) - } else { - return gradleUserHomeOutputDirs(from, to, stream) - } + def parts = [Pattern.quote(temporaryFolder.getTestDirectory().absolutePath) + ".*", "build", "transforms", "\\w+"] + return outputDirs(from, to, parts.join(quotedFileSeparator), stream) } Set gradleUserHomeOutputDirs(String from, String to, Closure stream = { output }) { diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIncrementalIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIncrementalIntegrationTest.groovy new file mode 100644 index 0000000000000..936e1135cd0b1 --- /dev/null +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIncrementalIntegrationTest.groovy @@ -0,0 +1,171 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.resolve.transform + +import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest + +class ArtifactTransformIncrementalIntegrationTest extends AbstractDependencyResolutionTest implements ArtifactTransformTestFixture { + + def "can query incremental changes"() { + settingsFile << """ + include 'a', 'b' + """ + + file("buildSrc/src/main/groovy/MakeGreen.groovy") << """ + import java.io.File + import javax.inject.Inject + import groovy.transform.CompileStatic + import org.gradle.api.provider.* + import org.gradle.api.file.* + import org.gradle.api.tasks.* + import org.gradle.api.artifacts.transform.* + import org.gradle.work.* + + abstract class MakeGreen implements TransformAction { + + interface Parameters extends TransformParameters { + @Internal + ListProperty getAddedFiles() + @Internal + ListProperty getModifiedFiles() + @Internal + ListProperty getRemovedFiles() + @Internal + Property getIncrementalExecution() + @Internal + Property getRegisterNewOutput() + } + + @Inject + abstract InputChanges getInputChanges() + + @InputArtifact + abstract Provider getInput() + + void transform(TransformOutputs outputs) { + println "Transforming " + input.get().asFile.name + println "incremental: " + inputChanges.incremental + assert parameters.incrementalExecution.get() == inputChanges.incremental + def changes = inputChanges.getFileChanges(input) + println "changes: \\n" + changes.join("\\n") + assert changes.findAll { it.changeType == ChangeType.ADDED }*.file as Set == resolveFiles(parameters.addedFiles.get()) + assert changes.findAll { it.changeType == ChangeType.REMOVED }*.file as Set == resolveFiles(parameters.removedFiles.get()) + assert changes.findAll { it.changeType == ChangeType.MODIFIED }*.file as Set == resolveFiles(parameters.modifiedFiles.get()) + def outputDirectory = outputs.dir("output") + changes.each { change -> + if (change.file != input.get().asFile) { + File outputFile = new File(outputDirectory, change.file.name) + switch (change.changeType) { + case ChangeType.ADDED: + case ChangeType.MODIFIED: + outputFile.text = change.file.text + break + case ChangeType.REMOVED: + outputFile.delete() + break + default: + throw new IllegalArgumentException() + } + } + } + } + + private resolveFiles(List files) { + files.collect { new File(input.get().asFile, it) } as Set + } + } + """ + + setupBuildFile() + + when: + previousExecution() + executer.withArguments("-PbContent=changed") + then: + executesIncrementally("ext.modified=['b']") + + when: + executer.withArguments('-PbNames=first,second,third') + then: + executesIncrementally(""" + ext.removed = ['b'] + ext.added = ['first', 'second', 'third'] + """) + + when: + executer.withArguments("-PbNames=first,second", "-PbContent=different") + then: + executesIncrementally(""" + ext.removed = ['third'] + ext.modified = ['first', 'second'] + """) + } + + private void setupBuildFile() { + buildFile .text = """ + ext { + added = [] + modified = [] + removed = [] + incremental = true + registerNewOutput = false + } + """ + setupBuildWithColorTransform { + produceDirs() + params(""" + addedFiles.set(provider { added }) + modifiedFiles.set(provider { modified }) + removedFiles.set(provider { removed }) + incrementalExecution.set(provider { incremental }) + incrementalExecution.set(provider { incremental }) + registerNewOutput.set(provider { project.registerNewOutput }) + """) + } + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + } + } + """ + } + + void executesNonIncrementally(String fileChanges = "ext.added = ['', 'b']") { + setupBuildFile() + buildFile << """ + ext.incremental = false + $fileChanges + """ + succeeds ":a:resolve" + outputContains("Transforming") + } + + void executesIncrementally(String fileChanges) { + setupBuildFile() + buildFile << """ + ext.incremental = true + $fileChanges + """ + succeeds ":a:resolve" + outputContains("Transforming") + } + + void previousExecution() { + executesNonIncrementally() + } +} diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformInputArtifactIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformInputArtifactIntegrationTest.groovy index a7543ffb6d8d3..c87d942af6e44 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformInputArtifactIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformInputArtifactIntegrationTest.groovy @@ -17,6 +17,7 @@ package org.gradle.integtests.resolve.transform import org.gradle.api.tasks.PathSensitivity +import org.gradle.initialization.StartParameterBuildOptions import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest import org.gradle.integtests.fixtures.DirectoryBuildCacheFixture import spock.lang.Unroll @@ -38,12 +39,13 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe tasks.producer.doLast { throw new RuntimeException('broken') } } - abstract class MakeGreen implements TransformAction { + abstract class MakeGreen implements TransformAction { @InputArtifact abstract File getInput() void transform(TransformOutputs outputs) { println "processing \${input.name}" + assert input.file } } """ @@ -59,8 +61,10 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe failure.assertHasCause("broken") } + // Documents existing behaviour. The absolute path of the input artifact is baked into the workspace identity + // and so when the path changes the outputs are invalidated @Unroll - def "can attach #description to input artifact property with project artifact file"() { + def "can attach #description to input artifact property with project artifact file but it has no effect when not caching"() { settingsFile << "include 'a', 'b', 'c'" setupBuildWithColorTransformAction() buildFile << """ @@ -71,7 +75,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - abstract class MakeGreen implements TransformAction { + abstract class MakeGreen implements TransformAction { @InputArtifact ${annotation} abstract File getInput() @@ -156,19 +160,22 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: succeeds(":a:resolve") - then: // have already seen these artifacts before + then: // have already seen these artifacts before, but the transform outputs have been been overwritten result.assertTasksNotSkipped(":b:producer", ":a:resolve") - transformed() + transformed("b.jar") outputContains("result = [b.jar.green, c.jar.green]") where: - description | annotation - "no sensitivity" | "" - "@PathSensitive(PathSensitivity.ABSOLUTE)" | "@PathSensitive(PathSensitivity.ABSOLUTE)" + description | annotation + "no sensitivity" | "" + "@PathSensitive(PathSensitivity.ABSOLUTE)" | "@PathSensitive(PathSensitivity.ABSOLUTE)" + "@PathSensitive(PathSensitivity.RELATIVE)" | "@PathSensitive(PathSensitivity.RELATIVE)" + "@PathSensitive(PathSensitivity.NAME_ONLY)" | "@PathSensitive(PathSensitivity.NAME_ONLY)" + "@PathSensitive(PathSensitivity.NONE)" | "@PathSensitive(PathSensitivity.NONE)" } @Unroll - def "can attach #description to input artifact property with project artifact directory"() { + def "can attach #description to input artifact property with project artifact directory but it has no effect when not caching"() { settingsFile << "include 'a', 'b', 'c'" setupBuildWithColorTransformAction { produceDirs() @@ -181,7 +188,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - abstract class MakeGreen implements TransformAction { + abstract class MakeGreen implements TransformAction { @InputArtifact ${annotation} abstract File getInput() @@ -192,7 +199,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe println "processing missing \${input.name}" } def output = outputs.file(input.name + ".green") - output.text = "green" + output.text = input.list().length + ".green" } } """ @@ -284,10 +291,20 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe transformed() outputContains("result = [b-blue.green, c-dir.green]") + when: + succeeds(":a:resolve") + + then: // have already seen these artifacts before, but the transform outputs have been been overwritten + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed("b-dir") + outputContains("result = [b-dir.green, c-dir.green]") + where: - description | annotation - "no sensitivity" | "" - "@PathSensitive(PathSensitivity.ABSOLUTE)" | "@PathSensitive(PathSensitivity.ABSOLUTE)" + description | annotation + "no sensitivity" | "" + "@PathSensitive(PathSensitivity.ABSOLUTE)" | "@PathSensitive(PathSensitivity.ABSOLUTE)" + "@PathSensitive(PathSensitivity.RELATIVE)" | "@PathSensitive(PathSensitivity.RELATIVE)" + "@PathSensitive(PathSensitivity.NAME_ONLY)" | "@PathSensitive(PathSensitivity.NAME_ONLY)" } def "re-runs transform when input artifact file changes from file to missing"() { @@ -301,7 +318,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - abstract class MakeGreen implements TransformAction { + abstract class MakeGreen implements TransformAction { @InputArtifact abstract File getInput() @@ -344,13 +361,52 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: succeeds(":a:resolve") - then: // seen these before + then: // seen these before, but the transform outputs have been overwritten result.assertTasksNotSkipped(":b:producer", ":a:resolve") - transformed() + transformed("b.jar") outputContains("result = [b.jar.green, c.jar.green]") } - def "can attach @PathSensitive(NONE) to input artifact property for project artifact file"() { + def "inputs to the build cache key are reported when build cache logging is enabled"() { + given: + settingsFile << "include 'a', 'b'" + setupBuildWithColorTransformAction() + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + } + } + + @CacheableTransform + abstract class MakeGreen implements TransformAction { + @PathSensitive(PathSensitivity.NONE) + @InputArtifact + abstract File getInput() + + void transform(TransformOutputs outputs) { + println "processing \${input.name}" + def output = outputs.file(input.text + ".green") + output.text = input.text + ".green" + } + } + """ + + when: + file("gradle.properties") << "${StartParameterBuildOptions.BuildCacheDebugLoggingOption.GRADLE_PROPERTY}=true" + withBuildCache().run ":a:resolve" + + then: + output.contains("Appending implementation to build cache key: MakeGreen") + output.contains("Appending input value fingerprint for 'inputPropertiesHash' to build cache key:") + output.contains("Appending input file fingerprints for 'inputArtifact' to build cache key:") + output.contains("Appending input file fingerprints for 'inputArtifactDependencies' to build cache key:") + output.contains("Appending output property name to build cache key: outputDirectory") + output.contains("Appending output property name to build cache key: resultsFile") + output.contains("Build cache key for MakeGreen") + } + + def "honors @PathSensitive(NONE) on input artifact property for project artifact file when caching"() { settingsFile << "include 'a', 'b', 'c'" setupBuildWithColorTransformAction() buildFile << """ @@ -361,7 +417,8 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - abstract class MakeGreen implements TransformAction { + @CacheableTransform + abstract class MakeGreen implements TransformAction { @PathSensitive(PathSensitivity.NONE) @InputArtifact abstract File getInput() @@ -375,7 +432,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe """ when: - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: result.assertTasksNotSkipped(":b:producer", ":c:producer", ":a:resolve") @@ -384,7 +441,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // path has changed, but should be up to date result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -393,7 +450,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out", "-PbFileName=b-blue.jar") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // name has changed, but should be up to date result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -401,7 +458,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b.green, c.green]") when: - executer.withArguments("-PbOutputDir=out", "-PbFileName=b-blue.jar", "-PbContent=b-new") + withBuildCache().executer.withArguments("-PbOutputDir=out", "-PbFileName=b-blue.jar", "-PbContent=b-new") succeeds(":a:resolve") then: // new content, should run @@ -410,7 +467,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b-new.green, c.green]") when: - executer.withArguments("-PbOutputDir=out", "-PbFileName=b-blue.jar", "-PbContent=b-new") + withBuildCache().executer.withArguments("-PbOutputDir=out", "-PbFileName=b-blue.jar", "-PbContent=b-new") succeeds(":a:resolve") then: // no change, should be up to date @@ -419,7 +476,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b-new.green, c.green]") when: - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // have already seen these artifacts before result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -428,7 +485,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } @Unroll - def "can attach @PathSensitive(#sensitivity) to input artifact property for project artifact directory"() { + def "honors @PathSensitive(#sensitivity) to input artifact property for project artifact directory when caching"() { settingsFile << "include 'a', 'b', 'c'" setupBuildWithColorTransformAction { produceDirs() @@ -441,7 +498,8 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - abstract class MakeGreen implements TransformAction { + @CacheableTransform + abstract class MakeGreen implements TransformAction { @PathSensitive(PathSensitivity.${sensitivity}) @InputArtifact abstract File getInput() @@ -449,13 +507,13 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe void transform(TransformOutputs outputs) { println "processing \${input.name}" def output = outputs.file(input.name + ".green") - output.text = "green" + output.text = input.list().length + ".green" } } """ when: - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: result.assertTasksNotSkipped(":b:producer", ":c:producer", ":a:resolve") @@ -464,7 +522,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // path has changed, but should be up to date result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -473,7 +531,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // name has changed, but should be up to date result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -482,7 +540,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue", "-PbContent=new") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // new content, should run result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -491,7 +549,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue", "-PbContent=new") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // no change, should be up to date result.assertTasksNotSkipped(":a:resolve") @@ -500,7 +558,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue", "-PbContent=new", "-PbName=new") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // new content (renamed file), should run result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -508,7 +566,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b-blue.green, c-dir.green]") when: - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // have already seen these artifacts before result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -516,11 +574,11 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b-dir.green, c-dir.green]") where: - sensitivity << [PathSensitivity.RELATIVE, PathSensitivity.NONE] + sensitivity << [PathSensitivity.NAME_ONLY, PathSensitivity.RELATIVE] } @Unroll - def "can attach @PathSensitive(#sensitivity) to input artifact property for project artifact file"() { + def "honors @PathSensitive(#sensitivity) on input artifact property for project artifact file when caching"() { settingsFile << "include 'a', 'b', 'c'" setupBuildWithColorTransformAction() buildFile << """ @@ -531,7 +589,8 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - abstract class MakeGreen implements TransformAction { + @CacheableTransform + abstract class MakeGreen implements TransformAction { @PathSensitive(PathSensitivity.${sensitivity}) @InputArtifact abstract File getInput() @@ -545,7 +604,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe """ when: - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: result.assertTasksNotSkipped(":b:producer", ":c:producer", ":a:resolve") @@ -554,7 +613,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // path has changed, but should be up to date result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -563,7 +622,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out", "-PbFileName=b-blue.jar") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // name has changed, should run result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -572,7 +631,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out", "-PbFileName=b-blue.jar", "-PbContent=new") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // new content, should run result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -581,7 +640,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe when: executer.withArguments("-PbOutputDir=out", "-PbFileName=b-blue.jar", "-PbContent=new") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // no change, should be up to date result.assertTasksNotSkipped(":a:resolve") @@ -589,7 +648,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b-blue.jar.green, c.jar.green]") when: - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") then: // have already seen these artifacts before result.assertTasksNotSkipped(":b:producer", ":a:resolve") @@ -600,7 +659,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe sensitivity << [PathSensitivity.RELATIVE, PathSensitivity.NAME_ONLY] } - def "can attach @PathSensitive(NAME_ONLY) to input artifact property for project artifact directory"() { + def "honors content changes for @PathSensitive(NONE) on input artifact property for project artifact directory when not caching"() { settingsFile << "include 'a', 'b', 'c'" setupBuildWithColorTransformAction { produceDirs() @@ -613,15 +672,15 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - abstract class MakeGreen implements TransformAction { - @PathSensitive(PathSensitivity.NAME_ONLY) + abstract class MakeGreen implements TransformAction { + @PathSensitive(PathSensitivity.NONE) @InputArtifact abstract File getInput() void transform(TransformOutputs outputs) { println "processing \${input.name}" def output = outputs.file(input.name + ".green") - output.text = "green" + output.text = input.list().length + ".green" } } """ @@ -638,16 +697,16 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe executer.withArguments("-PbOutputDir=out") succeeds(":a:resolve") - then: // path has changed, but should be up to date + then: // path has changed, but path is baked into workspace identity result.assertTasksNotSkipped(":b:producer", ":a:resolve") - transformed() + transformed("b-dir") outputContains("result = [b-dir.green, c-dir.green]") when: executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue") succeeds(":a:resolve") - then: // name has changed, should run + then: // name has changed, but path is baked into workspace identity result.assertTasksNotSkipped(":b:producer", ":a:resolve") transformed("b-blue") outputContains("result = [b-blue.green, c-dir.green]") @@ -674,9 +733,9 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue", "-PbContent=new", "-PbName=new") succeeds(":a:resolve") - then: // new content (renamed file), should run + then: // new content (renamed file), should not run result.assertTasksNotSkipped(":b:producer", ":a:resolve") - transformed("b-blue") + transformed() outputContains("result = [b-blue.green, c-dir.green]") when: @@ -688,6 +747,95 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b-dir.green, c-dir.green]") } + def "honors @PathSensitive(NONE) on input artifact property for project artifact directory when caching"() { + settingsFile << "include 'a', 'b', 'c'" + setupBuildWithColorTransformAction { + produceDirs() + } + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + implementation project(':c') + } + } + + @CacheableTransform + abstract class MakeGreen implements TransformAction { + @PathSensitive(PathSensitivity.NONE) + @InputArtifact + abstract File getInput() + + void transform(TransformOutputs outputs) { + println "processing \${input.name}" + def output = outputs.file(input.name + ".green") + output.text = input.list().length + ".green" + } + } + """ + + when: + withBuildCache().succeeds(":a:resolve") + + then: + result.assertTasksNotSkipped(":b:producer", ":c:producer", ":a:resolve") + transformed("b-dir", "c-dir") + outputContains("result = [b-dir.green, c-dir.green]") + + when: + executer.withArguments("-PbOutputDir=out") + withBuildCache().succeeds(":a:resolve") + + then: // path has changed, should be up to date + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed() + outputContains("result = [b-dir.green, c-dir.green]") + + when: + executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue") + withBuildCache().succeeds(":a:resolve") + + then: // name has changed, should be up to date + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed() + outputContains("result = [b-dir.green, c-dir.green]") + + when: + executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue", "-PbContent=new") + withBuildCache().succeeds(":a:resolve") + + then: // new content, should run + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed("b-blue") + outputContains("result = [b-blue.green, c-dir.green]") + + when: + executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue", "-PbContent=new") + withBuildCache().succeeds(":a:resolve") + + then: // no change, should be up to date + result.assertTasksNotSkipped(":a:resolve") + transformed() + outputContains("result = [b-blue.green, c-dir.green]") + + when: + executer.withArguments("-PbOutputDir=out", "-PbDirName=b-blue", "-PbContent=new", "-PbName=new") + withBuildCache().succeeds(":a:resolve") + + then: // new content (renamed file), should not run + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed() + outputContains("result = [b-blue.green, c-dir.green]") + + when: + withBuildCache().succeeds(":a:resolve") + + then: // have already seen these artifacts before + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed() + outputContains("result = [b-dir.green, c-dir.green]") + } + def "can attach @PathSensitive(NONE) to input artifact property for external artifact"() { setupBuildWithColorTransformAction() def lib1 = mavenRepo.module("group1", "lib", "1.0").adhocVariants().variant('runtime', [color: 'blue']).withModuleMetadata().publish() @@ -715,7 +863,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe implementation 'group2:lib2:1.0' } - abstract class MakeGreen implements TransformAction { + abstract class MakeGreen implements TransformAction { @PathSensitive(PathSensitivity.NONE) @InputArtifact abstract File getInput() @@ -801,7 +949,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe implementation 'group2:lib2:1.0' } - abstract class MakeGreen implements TransformAction { + abstract class MakeGreen implements TransformAction { @PathSensitive(PathSensitivity.${sensitivity}) @InputArtifact abstract File getInput() @@ -855,7 +1003,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } @Unroll - def "can attach @#annotation to input artifact property with project artifact file"() { + def "honors content changes with @#annotation on input artifact property with project artifact file when not caching"() { settingsFile << "include 'a', 'b', 'c'" setupBuildWithColorTransformAction { produceJars() @@ -868,7 +1016,7 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - abstract class MakeGreen implements TransformAction { + abstract class MakeGreen implements TransformAction { @InputArtifact @${annotation} abstract File getInput() @@ -915,36 +1063,46 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b.jar.green, c.jar.green]") when: - executer.withArguments("-PbContent=new", "-PbOutputDir=out") + executer.withArguments("-PbContent=new", "-PbTimestamp=567") succeeds(":a:resolve") - then: // path has changed, transforms up-to-date + then: // timestamp change only, should not run result.assertTasksNotSkipped(":b:producer", ":a:resolve") transformed() outputContains("result = [b.jar.green, c.jar.green]") when: - executer.withArguments("-PbContent=new", "-PbOutputDir=out", "-PbFileName=b-blue.jar") + executer.withArguments("-PbContent=new", "-PbTimestamp=567", "-PbOutputDir=out") succeeds(":a:resolve") - then: // new file name, transforms up-to-date + then: // path has changed, but path is baked into workspace identity result.assertTasksNotSkipped(":b:producer", ":a:resolve") - transformed() + transformed("b.jar") outputContains("result = [b.jar.green, c.jar.green]") when: + executer.withArguments("-PbContent=new", "-PbTimestamp=567", "-PbOutputDir=out", "-PbFileName=b-blue.jar") succeeds(":a:resolve") - then: // have already seen these artifacts before + then: // new file name, but path is baked into workspace identity result.assertTasksNotSkipped(":b:producer", ":a:resolve") - transformed() + transformed("b-blue.jar") + outputContains("result = [b-blue.jar.green, c.jar.green]") + + when: + succeeds(":a:resolve") + + then: // have already seen these artifacts before, but outputs have been overwritten + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed("b.jar") outputContains("result = [b.jar.green, c.jar.green]") where: annotation << ["Classpath", "CompileClasspath"] } - def "result is loaded from the build cache"() { + @Unroll + def "honors @#annotation on input artifact property with project artifact file when caching"() { settingsFile << "include 'a', 'b', 'c'" setupBuildWithColorTransformAction { produceJars() @@ -957,9 +1115,9 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe } } - @CacheableTransformAction - abstract class MakeGreen implements TransformAction { - @InputArtifact @Classpath + @CacheableTransform + abstract class MakeGreen implements TransformAction { + @InputArtifact @${annotation} abstract File getInput() void transform(TransformOutputs outputs) { @@ -979,29 +1137,116 @@ class ArtifactTransformInputArtifactIntegrationTest extends AbstractDependencyRe outputContains("result = [b.jar.green, c.jar.green]") when: - executer.withArguments("-PbTimestamp=5678") - succeeds(":a:resolve") + withBuildCache().succeeds(":a:resolve") + + then: // no change, should be up to date + result.assertTasksNotSkipped(":a:resolve") + transformed() + outputContains("result = [b.jar.green, c.jar.green]") + + when: + executer.withArguments("-PbContent=new") + withBuildCache().succeeds(":a:resolve") - then: // timestamp change without build cache + then: // new content, should run result.assertTasksNotSkipped(":b:producer", ":a:resolve") transformed("b.jar") outputContains("result = [b.jar.green, c.jar.green]") when: - executer.withArguments("-PbTimestamp=7890") + executer.withArguments("-PbContent=new") withBuildCache().succeeds(":a:resolve") - then: // timestamp change, pulled from cache + then: // no change, should be up to date + result.assertTasksNotSkipped(":a:resolve") + transformed() + outputContains("result = [b.jar.green, c.jar.green]") + + when: + executer.withArguments("-PbContent=new", "-PbTimestamp=567") + withBuildCache().succeeds(":a:resolve") + + then: // timestamp change only, should not run result.assertTasksNotSkipped(":b:producer", ":a:resolve") transformed() outputContains("result = [b.jar.green, c.jar.green]") when: - executer.withArguments("-PbTimestamp=7890") - succeeds(":a:resolve") + executer.withArguments("-PbContent=new", "-PbTimestamp=567", "-PbOutputDir=out") + withBuildCache().succeeds(":a:resolve") - then: // no change, up-to-date - result.assertTasksNotSkipped(":a:resolve") + then: // path has changed, should not run + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed() + outputContains("result = [b.jar.green, c.jar.green]") + + when: + executer.withArguments("-PbContent=new", "-PbTimestamp=567", "-PbOutputDir=out", "-PbFileName=b-blue.jar") + withBuildCache().succeeds(":a:resolve") + + then: // new file name, should not run + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed() + outputContains("result = [b.jar.green, c.jar.green]") + + when: + withBuildCache().succeeds(":a:resolve") + + then: // have already seen these artifacts before, should not run + result.assertTasksNotSkipped(":b:producer", ":a:resolve") + transformed() + outputContains("result = [b.jar.green, c.jar.green]") + + where: + annotation << ["Classpath", "CompileClasspath"] + } + + def "honors runtime classpath normalization for input artifact"() { + settingsFile << "include 'a', 'b', 'c'" + setupBuildWithColorTransformAction { + produceJars() + } + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + implementation project(':c') + } + + normalization { + runtimeClasspath { + ignore("ignored.txt") + } + } + } + + abstract class MakeGreen implements TransformAction { + @InputArtifact @Classpath + abstract File getInput() + + void transform(TransformOutputs outputs) { + println "processing \${input.name}" + def output = outputs.file(input.name + ".green") + output.text = input.text + ".green" + } + } + """ + + when: + executer.withArguments("-PbEntryName=ignored.txt") + run(":a:resolve") + + then: + result.assertTasksNotSkipped(":b:producer", ":c:producer", ":a:resolve") + transformed("b.jar", "c.jar") + outputContains("result = [b.jar.green, c.jar.green]") + + when: + executer.withArguments("-PbEntryName=ignored.txt", "-PbContent=different") + run(":a:resolve") + + then: // change is ignored due to normalization + result.assertTasksNotSkipped(":b:producer", ":a:resolve") transformed() outputContains("result = [b.jar.green, c.jar.green]") } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIntegrationTest.groovy index e6ea0964af016..690d459d424e5 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIntegrationTest.groovy @@ -19,17 +19,13 @@ package org.gradle.integtests.resolve.transform import org.gradle.api.internal.artifacts.transform.ExecuteScheduledTransformationStepBuildOperationType import org.gradle.integtests.fixtures.AbstractHttpDependencyResolutionTest import org.gradle.integtests.fixtures.BuildOperationsFixture -import org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner import org.gradle.internal.file.FileType import org.hamcrest.Matcher -import org.junit.runner.RunWith import spock.lang.Issue import spock.lang.Unroll -import static org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner.configureIncrementalArtifactTransformations import static org.gradle.util.Matchers.matchesRegexp -@RunWith(ExperimentalIncrementalArtifactTransformationsRunner) class ArtifactTransformIntegrationTest extends AbstractHttpDependencyResolutionTest { def setup() { settingsFile << """ @@ -37,9 +33,10 @@ class ArtifactTransformIntegrationTest extends AbstractHttpDependencyResolutionT include 'lib' include 'app' """ - configureIncrementalArtifactTransformations(settingsFile) buildFile << """ +import org.gradle.api.artifacts.transform.TransformParameters + def usage = Attribute.of('usage', String) def artifactType = Attribute.of('artifactType', String) def extraAttribute = Attribute.of('extra', String) @@ -802,13 +799,13 @@ $fileSizer } dependencies { - registerTransformAction(IdentityTransform) { + registerTransform(IdentityTransform) { from.attribute(artifactType, 'jar') to.attribute(artifactType, 'identity') } } - abstract class IdentityTransform implements TransformAction { + abstract class IdentityTransform implements TransformAction { @InputArtifact abstract File getInput() @@ -1032,6 +1029,7 @@ Found the following transforms: } variant3 { attributes.attribute(buildType, 'debug') + attributes.attribute(flavor, 'free') artifact jar1 } } @@ -1112,11 +1110,13 @@ Found the following transforms: - With source attributes: - artifactType 'jar' - buildType 'debug' + - flavor 'free' - usage 'api' - Candidate transform(s): - Transform 'BrokenTransform' producing attributes: - artifactType 'transformed' - buildType 'debug' + - flavor 'free' - usage 'api'""" } @@ -1558,7 +1558,7 @@ Found the following transforms: compile files(a) } - class FailingTransformAction implements TransformAction { + abstract class FailingTransform implements TransformAction { void transform(TransformOutputs outputs) { ${switch (type) { case FileType.Missing: @@ -1580,7 +1580,7 @@ Found the following transforms: }} } } - ${declareTransformAction('FailingTransformAction')} + ${declareTransformAction('FailingTransform')} task resolve(type: Copy) { def artifacts = configurations.compile.incoming.artifactView { @@ -1628,7 +1628,7 @@ Found the following transforms: compile files(a) } - class DirectoryTransformAction implements TransformAction { + abstract class DirectoryTransform implements TransformAction { void transform(TransformOutputs outputs) { def outputFile = outputs.file("some/dir/output.txt") assert outputFile.parentFile.directory @@ -1638,7 +1638,7 @@ Found the following transforms: new File(outputDir, "in-dir.txt").text = "another output" } } - ${declareTransformAction('DirectoryTransformAction')} + ${declareTransformAction('DirectoryTransform')} task resolve(type: Copy) { def artifacts = configurations.compile.incoming.artifactView { @@ -1666,7 +1666,7 @@ Found the following transforms: compile files(a) } - abstract class MyTransformAction implements TransformAction { + abstract class MyTransform implements TransformAction { @InputArtifact abstract File getInput() @@ -1677,7 +1677,7 @@ Found the following transforms: } } dependencies { - registerTransformAction(MyTransformAction) { + registerTransform(MyTransform) { from.attribute(artifactType, 'directory') to.attribute(artifactType, 'size') } @@ -1744,7 +1744,7 @@ Found the following transforms: SomewhereElseTransform.output = file("other.jar") - class SomewhereElseTransform implements TransformAction { + abstract class SomewhereElseTransform implements TransformAction { static def output void transform(TransformOutputs outputs) { def outputFile = outputs.file(output) @@ -1952,14 +1952,13 @@ Found the following transforms: String toString() { return "" } } - @AssociatedTransformAction(CustomAction) - interface Custom { - @Input - CustomType getInput() - void setInput(CustomType input) - } - - class CustomAction implements TransformAction { + abstract class Custom implements TransformAction { + interface Parameters extends TransformParameters { + @Input + CustomType getInput() + void setInput(CustomType input) + } + void transform(TransformOutputs outputs) { } } @@ -1985,7 +1984,7 @@ Found the following transforms: when: fails "resolve" then: - Matcher matchesCannotIsolate = matchesRegexp("Cannot isolate parameters Custom\\\$Inject@.* of artifact transform CustomAction") + Matcher matchesCannotIsolate = matchesRegexp("Cannot isolate parameters Custom\\\$Parameters\\\$Inject@.* of artifact transform Custom") if (scheduled) { failure.assertThatDescription(matchesCannotIsolate) } else { @@ -2311,7 +2310,7 @@ Found the following transforms: def declareTransformAction(String transformActionImplementation) { """ dependencies { - registerTransformAction($transformActionImplementation) { + registerTransform($transformActionImplementation) { from.attribute(artifactType, 'jar') to.attribute(artifactType, 'size') } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIsolationIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIsolationIntegrationTest.groovy index 5bde248d22e78..6aa97f2042f63 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIsolationIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformIsolationIntegrationTest.groovy @@ -85,23 +85,17 @@ class Resolve extends Copy { given: buildFile << """ - @AssociatedTransformAction(CountRecorderAction) - interface CountRecorder { - @Input - Counter getCounter() - void setCounter(Counter counter) - } - - abstract class CountRecorderAction implements TransformAction { - private final Counter counter; - - @TransformParameters - abstract CountRecorder getParameters() + abstract class CountRecorder implements TransformAction { + interface Parameters extends TransformParameters{ + @Input + Counter getCounter() + void setCounter(Counter counter) + } @InputArtifact abstract File getInput() - CountRecorderAction() { + CountRecorder() { println "Creating CountRecorder" } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformParallelIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformParallelIntegrationTest.groovy index 3ef961617d316..98c5a720ac991 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformParallelIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformParallelIntegrationTest.groovy @@ -17,15 +17,10 @@ package org.gradle.integtests.resolve.transform import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest -import org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner import org.gradle.integtests.fixtures.build.BuildTestFile import org.gradle.test.fixtures.server.http.BlockingHttpServer import org.junit.Rule -import org.junit.runner.RunWith -import static org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner.configureIncrementalArtifactTransformations - -@RunWith(ExperimentalIncrementalArtifactTransformationsRunner) class ArtifactTransformParallelIntegrationTest extends AbstractDependencyResolutionTest { @Rule BlockingHttpServer server = new BlockingHttpServer() @@ -38,8 +33,6 @@ class ArtifactTransformParallelIntegrationTest extends AbstractDependencyResolut private void setupBuild(BuildTestFile buildTestFile) { buildTestFile.with { - configureIncrementalArtifactTransformations(buildTestFile.settingsFile) - settingsFile << """ rootProject.name = '${rootProjectName}' """ diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformTestFixture.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformTestFixture.groovy index 24fe09042934f..7bc3dc5a838c2 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformTestFixture.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformTestFixture.groovy @@ -51,6 +51,8 @@ trait ArtifactTransformTestFixture { buildFile << """ import ${javax.inject.Inject.name} +// TODO: Default imports should work for of inner classes +import ${org.gradle.api.artifacts.transform.TransformParameters.name} def color = Attribute.of('color', String) allprojects { @@ -129,13 +131,15 @@ class JarProducer extends DefaultTask { String content = "content" @Input long timestamp = 123L + @Input + String entryName = "thing.class" @TaskAction def go() { def file = output.get().asFile file.withOutputStream { def jarFile = new JarOutputStream(it) - def entry = new ZipEntry("thing.class") + def entry = new ZipEntry(entryName) entry.time = timestamp jarFile.putNextEntry(entry) jarFile << content @@ -156,7 +160,7 @@ class JarProducer extends DefaultTask { buildFile << """ allprojects { dependencies { - registerTransformAction(MakeGreen) { + registerTransform(MakeGreen) { from.attribute(color, 'blue') to.attribute(color, 'green') } @@ -183,9 +187,11 @@ allprojects { p -> registerTransform(MakeGreen) { from.attribute(color, 'blue') to.attribute(color, 'green') + ${builder.transformParamsConfig.empty ? "" : """ parameters { ${builder.transformParamsConfig} } + """} } } } @@ -257,6 +263,9 @@ allprojects { p -> if (project.hasProperty("\${project.name}Timestamp")) { timestamp = Long.parseLong(project.property("\${project.name}Timestamp")) } + if (project.hasProperty("\${project.name}EntryName")) { + entryName = project.property("\${project.name}EntryName") + } } """.stripIndent() } @@ -290,6 +299,9 @@ allprojects { p -> if (project.hasProperty("\${project.name}Name")) { names = [project.property("\${project.name}Name")] } + if (project.hasProperty("\${project.name}Names")) { + names.set(project.property("\${project.name}Names").split(',') as List) + } if (project.hasProperty("\${project.name}DirName")) { output = layout.buildDir.dir(project.property("\${project.name}DirName")) } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformValuesInjectionIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformValuesInjectionIntegrationTest.groovy index da0a1eec1a278..9fdd1190d9ab8 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformValuesInjectionIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformValuesInjectionIntegrationTest.groovy @@ -16,10 +16,11 @@ package org.gradle.integtests.resolve.transform +import com.google.common.reflect.TypeToken import org.gradle.api.artifacts.transform.InputArtifact import org.gradle.api.artifacts.transform.InputArtifactDependencies -import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.file.FileCollection +import org.gradle.api.provider.Provider import org.gradle.api.tasks.Console import org.gradle.api.tasks.Destroys import org.gradle.api.tasks.Input @@ -27,6 +28,7 @@ import org.gradle.api.tasks.InputDirectory import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.Internal import org.gradle.api.tasks.LocalState +import org.gradle.api.tasks.Nested import org.gradle.api.tasks.OutputDirectories import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.OutputFile @@ -39,7 +41,8 @@ import static org.gradle.util.Matchers.matchesRegexp class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependencyResolutionTest implements ArtifactTransformTestFixture { - def "transform can receive parameters, workspace and input artifact via abstract getter"() { + @Unroll + def "transform can receive parameters, workspace and input artifact (#inputArtifactType) via abstract getter"() { settingsFile << """ include 'a', 'b', 'c' """ @@ -64,22 +67,20 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency } } - @AssociatedTransformAction(MakeGreenAction) - interface MakeGreen { - @Input - String getExtension() - void setExtension(String value) - } - - abstract class MakeGreenAction implements TransformAction { - @TransformParameters - abstract MakeGreen getParameters() + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + @Input + String getExtension() + void setExtension(String value) + } + @InputArtifact - abstract File getInput() + abstract ${inputArtifactType} getInput() void transform(TransformOutputs outputs) { - println "processing \${input.name}" - def output = outputs.file(input.name + "." + parameters.extension) + File inputFile = input${convertToFile} + println "processing \${inputFile.name}" + def output = outputs.file(inputFile.name + "." + parameters.extension) output.text = "ok" } } @@ -92,17 +93,141 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency outputContains("processing b.jar") outputContains("processing c.jar") outputContains("result = [b.jar.green, c.jar.green]") + + where: + inputArtifactType | convertToFile + 'File' | '' + 'Provider' | '.get().asFile' } - def "transform parameters are validated for input output annotations"() { + @Unroll + def "transform can receive parameter of type #type"() { settingsFile << """ include 'a', 'b', 'c' """ + setupBuildWithColorTransform { + params(""" + prop.set(${value}) + """) + } + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + implementation project(':c') + } + } + + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + @Input + ${type} getProp() + @Input @Optional + ${type} getOtherProp() + } + + void transform(TransformOutputs outputs) { + println "processing using " + parameters.prop.get() + assert parameters.otherProp.getOrNull() == ${expectedNullValue} + } + } +""" + when: + run("a:resolve") + + then: + outputContains("processing using ${expected}") + + where: + type | value | expected | expectedNullValue + "Property" | "'value'" | 'value' | null + "ListProperty" | "['a', 'b']" | "[a, b]" | "[]" + "SetProperty" | "['a', 'b']" | "[a, b]" | "[] as Set" + "MapProperty" | "[a: 1, b: 2]" | "[a:1, b:2]" | "[:]" + } + + def "transform parameters are validated for input output annotations"() { + settingsFile << """ + include 'a', 'b' + """ setupBuildWithColorTransform { params(""" extension = 'green' """) } + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + } + } + + @CacheableTransform + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + String getExtension() + void setExtension(String value) + + @OutputDirectory + File getOutputDir() + void setOutputDir(File outputDir) + + @Input + String getMissingInput() + void setMissingInput(String missing) + + @Input + File getFileInput() + void setFileInput(File file) + + @InputFiles + ConfigurableFileCollection getNoPathSensitivity() + + @InputFile + File getNoPathSensitivityFile() + void setNoPathSensitivityFile(File file) + + @InputDirectory + File getNoPathSensitivityDir() + void setNoPathSensitivityDir(File file) + + @PathSensitive(PathSensitivity.ABSOLUTE) + @InputFiles + ConfigurableFileCollection getAbsolutePathSensitivity() + } + + void transform(TransformOutputs outputs) { + throw new RuntimeException() + } + } +""" + + when: + fails(":a:resolve") + + then: + failure.assertThatDescription(matchesRegexp('Cannot isolate parameters MakeGreen\\$Parameters\\$Inject@.* of artifact transform MakeGreen')) + failure.assertHasCause('Some problems were found with the configuration of the artifact transform parameter MakeGreen.Parameters.') + assertPropertyValidationErrors( + extension: 'is not annotated with an input annotation', + outputDir: 'is annotated with unsupported annotation @OutputDirectory', + missingInput: 'does not have a value specified', + fileInput: [ + 'has @Input annotation used on property of type java.io.File', + 'does not have a value specified' + ], + absolutePathSensitivity: 'is declared to be sensitive to absolute paths. This is not allowed for cacheable transforms', + noPathSensitivity: 'is declared without path sensitivity. Properties of cacheable transforms must declare their path sensitivity', + noPathSensitivityDir: 'is declared without path sensitivity. Properties of cacheable transforms must declare their path sensitivity', + noPathSensitivityFile: 'is declared without path sensitivity. Properties of cacheable transforms must declare their path sensitivity' + ) + } + + def "cannot query parameters for transform without parameters"() { + settingsFile << """ + include 'a', 'b', 'c' + """ + setupBuildWithColorTransform() buildFile << """ project(':a') { dependencies { @@ -111,27 +236,45 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency } } - @AssociatedTransformAction(MakeGreenAction) - interface MakeGreen { - String getExtension() - void setExtension(String value) - @OutputDirectory - File getOutputDir() - void setOutputDir(File outputDir) - @Input - String getMissingInput() - void setMissingInput(String missing) - @InputFiles - ConfigurableFileCollection getNoPathSensitivity() - @PathSensitive(PathSensitivity.ABSOLUTE) - @InputFiles - ConfigurableFileCollection getAbsolutePathSensitivity() + abstract class MakeGreen implements TransformAction { + void transform(TransformOutputs outputs) { + println getParameters() + } + } +""" + + when: + fails(":a:resolve") + + then: + failure.assertResolutionFailure(':a:implementation') + failure.assertHasCause("Cannot query parameters for artifact transform without parameters.") + } + + def "transform parameters type cannot use caching annotations"() { + settingsFile << """ + include 'a', 'b', 'c' + """ + setupBuildWithColorTransform { + params(""" + extension = 'green' + """) + } + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + implementation project(':c') + } } - @CacheableTransformAction - abstract class MakeGreenAction implements TransformAction { - @TransformParameters - abstract MakeGreen getParameters() + abstract class MakeGreen implements TransformAction { + @CacheableTask @CacheableTransform + interface Parameters extends TransformParameters { + @Input + String getExtension() + void setExtension(String value) + } void transform(TransformOutputs outputs) { throw new RuntimeException() @@ -143,19 +286,16 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency fails(":a:resolve") then: - failure.assertThatDescription(matchesRegexp('Cannot isolate parameters MakeGreen\\$Inject@.* of artifact transform MakeGreenAction')) - failure.assertHasCause('Some problems were found with the configuration of the artifact transform parameter MakeGreen.') - failure.assertHasCause("Property 'extension' is not annotated with an input annotation.") - failure.assertHasCause("Property 'outputDir' is annotated with unsupported annotation @OutputDirectory.") - failure.assertHasCause("Property 'missingInput' does not have a value specified.") - failure.assertHasCause("Property 'absolutePathSensitivity' is declared to be sensitive to absolute paths. This is not allowed for cacheable transforms.") - failure.assertHasCause("Property 'noPathSensitivity' is declared without path sensitivity. Properties of cacheable transforms must declare their path sensitivity.") + failure.assertThatDescription(matchesRegexp('Cannot isolate parameters MakeGreen\\$Parameters\\$Inject@.* of artifact transform MakeGreen')) + failure.assertHasCause('Some problems were found with the configuration of the artifact transform parameter MakeGreen.Parameters.') + failure.assertHasCause("Cannot use @CacheableTask with type MakeGreen.Parameters. This annotation can only be used with Task types.") + failure.assertHasCause("Cannot use @CacheableTransform with type MakeGreen.Parameters. This annotation can only be used with TransformAction types.") } @Unroll def "transform parameters type cannot use annotation @#annotation.simpleName"() { settingsFile << """ - include 'a', 'b', 'c' + include 'a', 'b' """ setupBuildWithColorTransform { params(""" @@ -166,24 +306,19 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency project(':a') { dependencies { implementation project(':b') - implementation project(':c') } } - @AssociatedTransformAction(MakeGreenAction) - interface MakeGreen { - @Input - String getExtension() - void setExtension(String value) - @${annotation.simpleName} - String getBad() - void setBad(String value) - } + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + @Input + String getExtension() + void setExtension(String value) + @${annotation.simpleName} + String getBad() + void setBad(String value) + } - abstract class MakeGreenAction implements TransformAction { - @TransformParameters - abstract MakeGreen getParameters() - void transform(TransformOutputs outputs) { throw new RuntimeException() } @@ -194,12 +329,12 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency fails(":a:resolve") then: - failure.assertThatDescription(matchesRegexp('Cannot isolate parameters MakeGreen\\$Inject@.* of artifact transform MakeGreenAction')) - failure.assertHasCause('A problem was found with the configuration of the artifact transform parameter MakeGreen.') - failure.assertHasCause("Property 'bad' is annotated with unsupported annotation @${annotation.simpleName}.") + failure.assertThatDescription(matchesRegexp('Cannot isolate parameters MakeGreen\\$Parameters\\$Inject@.* of artifact transform MakeGreen')) + failure.assertHasCause('A problem was found with the configuration of the artifact transform parameter MakeGreen.Parameters.') + assertPropertyValidationErrors(bad: "is annotated with unsupported annotation @${annotation.simpleName}") where: - annotation << [OutputFile, OutputFiles, OutputDirectory, OutputDirectories, Destroys, LocalState, OptionValues] + annotation << [OutputFile, OutputFiles, OutputDirectory, OutputDirectories, Destroys, LocalState, OptionValues, Nested] } @Unroll @@ -220,19 +355,15 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency } } - @AssociatedTransformAction(MakeGreenAction) - interface MakeGreen { - String getExtension() - void setExtension(String value) - @${annotation.simpleName} - String getBad() - void setBad(String value) - } + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + String getExtension() + void setExtension(String value) + @${annotation.simpleName} + String getBad() + void setBad(String value) + } - abstract class MakeGreenAction implements TransformAction { - @TransformParameters - abstract MakeGreen getParameters() - void transform(TransformOutputs outputs) { throw new RuntimeException() } @@ -244,12 +375,12 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency then: failure.assertHasDescription('A problem occurred evaluating root project') - failure.assertHasCause('Could not create an instance of type MakeGreen.') - failure.assertHasCause('Could not generate a decorated class for interface MakeGreen.') - failure.assertHasCause("Cannot use @${annotation.simpleName} annotation on method MakeGreen.getBad().") + failure.assertHasCause('Could not create an instance of type MakeGreen$Parameters.') + failure.assertHasCause('Could not generate a decorated class for interface MakeGreen$Parameters.') + failure.assertHasCause("Cannot use @${annotation.simpleName} annotation on method Parameters.getBad().") where: - annotation << [InputArtifact, InputArtifactDependencies, TransformParameters] + annotation << [InputArtifact, InputArtifactDependencies] } def "transform action is validated for input output annotations"() { @@ -269,18 +400,14 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency } } - @AssociatedTransformAction(MakeGreenAction) - interface MakeGreen { - @Input - String getExtension() - void setExtension(String value) - } + @CacheableTransform + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + @Input + String getExtension() + void setExtension(String value) + } - @CacheableTransformAction - abstract class MakeGreenAction implements TransformAction { - @TransformParameters - abstract MakeGreen getParameters() - @InputFile File inputFile @@ -308,13 +435,47 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency then: failure.assertHasDescription('A problem occurred evaluating root project') - failure.assertHasCause('Some problems were found with the configuration of MakeGreenAction.') - failure.assertHasCause("Property 'conflictingAnnotations' is annotated with unsupported annotation @InputFile.") - failure.assertHasCause("Property 'conflictingAnnotations' has conflicting property types declared: @InputArtifact, @InputArtifactDependencies.") - failure.assertHasCause("Property 'inputFile' is annotated with unsupported annotation @InputFile.") - failure.assertHasCause("Property 'notAnnotated' is not annotated with an input annotation.") - failure.assertHasCause("Property 'noPathSensitivity' is declared without path sensitivity. Properties of cacheable transforms must declare their path sensitivity.") - failure.assertHasCause("Property 'absolutePathSensitivityDependencies' is declared to be sensitive to absolute paths. This is not allowed for cacheable transforms.") + failure.assertHasCause('Some problems were found with the configuration of MakeGreen.') + assertPropertyValidationErrors( + 'conflictingAnnotations': [ + 'is annotated with unsupported annotation @InputFile', + 'has conflicting property types declared: @InputArtifact, @InputArtifactDependencies' + ], + inputFile: 'is annotated with unsupported annotation @InputFile', + notAnnotated: 'is not annotated with an input annotation', + noPathSensitivity: 'is declared without path sensitivity. Properties of cacheable transforms must declare their path sensitivity', + absolutePathSensitivityDependencies: 'is declared to be sensitive to absolute paths. This is not allowed for cacheable transforms' + ) + } + + def "transform action type cannot use cacheable task annotation"() { + settingsFile << """ + include 'a', 'b', 'c' + """ + setupBuildWithColorTransform() + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + implementation project(':c') + } + } + + @CacheableTask + abstract class MakeGreen implements TransformAction { + void transform(TransformOutputs outputs) { + throw new RuntimeException() + } + } +""" + + when: + fails(":a:resolve") + + then: + failure.assertHasDescription('A problem occurred evaluating root project') + failure.assertHasCause('A problem was found with the configuration of MakeGreen.') + failure.assertHasCause("Cannot use @CacheableTask with type MakeGreen. This annotation can only be used with Task types.") } @Unroll @@ -335,14 +496,13 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency } } - @AssociatedTransformAction(MakeGreenAction) - interface MakeGreen { - @Input - String getExtension() - void setExtension(String value) - } + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + @Input + String getExtension() + void setExtension(String value) + } - abstract class MakeGreenAction implements TransformAction { @${annotation.simpleName} String getBad() { } @@ -357,11 +517,11 @@ class ArtifactTransformValuesInjectionIntegrationTest extends AbstractDependency then: failure.assertHasDescription('A problem occurred evaluating root project') - failure.assertHasCause('A problem was found with the configuration of MakeGreenAction.') - failure.assertHasCause("Property 'bad' is annotated with unsupported annotation @${annotation.simpleName}.") + failure.assertHasCause('A problem was found with the configuration of MakeGreen.') + assertPropertyValidationErrors(bad: "is annotated with unsupported annotation @${annotation.simpleName}") where: - annotation << [Input, InputFile, InputDirectory, OutputFile, OutputFiles, OutputDirectory, OutputDirectories, Destroys, LocalState, OptionValues, Console, Internal] + annotation << [Input, InputFile, InputDirectory, OutputFile, OutputFiles, OutputDirectory, OutputDirectories, Destroys, LocalState, OptionValues, Console, Internal, Nested] } @Unroll @@ -383,7 +543,7 @@ project(':b') { } } -abstract class MakeGreen implements TransformAction { +abstract class MakeGreen implements TransformAction { @InputArtifactDependencies abstract ${targetType} getDependencies() @InputArtifact @@ -455,10 +615,10 @@ abstract class MakeGreen extends ArtifactTransform { failure.assertHasCause("Cannot use @${annotation.simpleName} annotation on method MakeGreen.getInputFile().") where: - annotation << [InputArtifact, InputArtifactDependencies, TransformParameters] + annotation << [InputArtifact, InputArtifactDependencies] } - def "transform cannot receive parameter object via constructor parameter"() { + def "transform can receive parameter object via constructor parameter"() { settingsFile << """ include 'a', 'b', 'c' """ @@ -475,18 +635,17 @@ abstract class MakeGreen extends ArtifactTransform { } } - @AssociatedTransformAction(MakeGreenAction) - interface MakeGreen { - @Input - String getExtension() - void setExtension(String value) - } + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + @Input + String getExtension() + void setExtension(String value) + } - class MakeGreenAction implements TransformAction { - private MakeGreen conf + private Parameters conf @Inject - MakeGreenAction(MakeGreen conf) { + MakeGreen(Parameters conf) { this.conf = conf } @@ -495,18 +654,12 @@ abstract class MakeGreen extends ArtifactTransform { } """ - when: - fails(":a:resolve") - - then: - // Documents existing behaviour. Should fail eagerly and with a better error message - failure.assertHasDescription("Execution failed for task ':a:resolve'.") - failure.assertHasCause("Execution failed for MakeGreenAction: ${file('b/build/b.jar')}.") - failure.assertHasCause("Unable to determine constructor argument #1: missing parameter of interface MakeGreen, or no service of type interface MakeGreen") + expect: + succeeds(":a:resolve") } @Unroll - def "transform cannot use @InputArtifact to receive dependencies"() { + def "transform cannot use @InputArtifact to receive #propertyType"() { settingsFile << """ include 'a', 'b', 'c' """ @@ -519,12 +672,12 @@ project(':a') { } } -abstract class MakeGreen implements TransformAction { +abstract class MakeGreen implements TransformAction { @InputArtifact - abstract FileCollection getDependencies() + abstract ${propertyType instanceof Class ? propertyType.name : propertyType} getInput() void transform(TransformOutputs outputs) { - dependencies.files + input throw new RuntimeException("broken") } } @@ -537,7 +690,10 @@ abstract class MakeGreen implements TransformAction { // Documents existing behaviour. Should fail eagerly and with a better error message failure.assertHasDescription("Execution failed for task ':a:resolve'.") failure.assertHasCause("Execution failed for MakeGreen: ${file('b/build/b.jar')}.") - failure.assertHasCause("No service of type interface ${FileCollection.name} available.") + failure.assertHasCause("No service of type ${propertyType} available.") + + where: + propertyType << [FileCollection, new TypeToken>() {}.getType(), new TypeToken>() {}.getType()] } def "transform cannot use @Inject to receive input file"() { @@ -553,7 +709,7 @@ project(':a') { } } -abstract class MakeGreen implements TransformAction { +abstract class MakeGreen implements TransformAction { @Inject abstract File getWorkspace() @@ -574,6 +730,50 @@ abstract class MakeGreen implements TransformAction { failure.assertHasCause("No service of type class ${File.name} available.") } + def "task implementation cannot use cacheable transform annotation"() { + buildFile << """ + @CacheableTransform + class MyTask extends DefaultTask { + File getThing() { null } + } + + tasks.create('broken', MyTask) + """ + + expect: + fails('broken') + failure.assertHasDescription("A problem occurred evaluating root project") + failure.assertHasCause("Could not create task ':broken'.") + failure.assertHasCause("A problem was found with the configuration of task ':broken'.") + failure.assertHasCause("Cannot use @CacheableTransform with type MyTask. This annotation can only be used with TransformAction types.") + } + + def "task @Nested bean cannot use cacheable annotations"() { + buildFile << """ + class MyTask extends DefaultTask { + @Nested + Options getThing() { new Options() } + + @TaskAction + void go() { } + } + + @CacheableTransform @CacheableTask + class Options { + } + + tasks.create('broken', MyTask) + """ + + expect: + // Probably should be eager + fails('broken') + failure.assertHasDescription("Could not determine the dependencies of task ':broken'.") + failure.assertHasCause("Some problems were found with the configuration of task ':broken'.") + failure.assertHasCause("Cannot use @CacheableTask with type Options. This annotation can only be used with Task types.") + failure.assertHasCause("Cannot use @CacheableTransform with type Options. This annotation can only be used with TransformAction types.") + } + @Unroll def "task implementation cannot use injection annotation @#annotation.simpleName"() { buildFile << """ @@ -587,11 +787,24 @@ abstract class MakeGreen implements TransformAction { expect: fails('broken') + failure.assertHasDescription("A problem occurred evaluating root project") failure.assertHasCause("Could not create task of type 'MyTask'.") failure.assertHasCause("Could not generate a decorated class for class MyTask.") failure.assertHasCause("Cannot use @${annotation.simpleName} annotation on method MyTask.getThing().") where: - annotation << [InputArtifact, InputArtifactDependencies, TransformParameters] + annotation << [InputArtifact, InputArtifactDependencies] + } + + void assertPropertyValidationErrors(Map validationErrors) { + int count = 0 + validationErrors.each { propertyName, errorMessageOrMessages -> + def errorMessages = errorMessageOrMessages instanceof Iterable ? [*errorMessageOrMessages] : [errorMessageOrMessages] + errorMessages.each { errorMessage -> + count++ + failure.assertHasCause("Property '${propertyName}' ${errorMessage}.") + } + } + assert errorOutput.count("> Property") == count } } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformWithDependenciesIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformWithDependenciesIntegrationTest.groovy index 817c007df7f56..5d48ab9e560ca 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformWithDependenciesIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformWithDependenciesIntegrationTest.groovy @@ -53,13 +53,6 @@ class ArtifactTransformWithDependenciesIntegrationTest extends AbstractHttpDepen setupBuildWithColorAttributes(cl) buildFile << """ -@AssociatedTransformAction(TestTransformAction) -interface TestTransform { - @Input - String getTransformName() - void setTransformName(String name) -} - allprojects { repositories { maven { @@ -108,10 +101,12 @@ project(':app') { import javax.inject.Inject -abstract class TestTransformAction implements TransformAction { - - @TransformParameters - abstract TestTransform getParameters() +abstract class TestTransform implements TransformAction { + interface Parameters extends TransformParameters { + @Input + String getTransformName() + void setTransformName(String name) + } @InputArtifactDependencies abstract FileCollection getInputArtifactDependencies() @@ -131,7 +126,7 @@ abstract class TestTransformAction implements TransformAction { } } -abstract class SimpleTransform implements TransformAction { +abstract class SimpleTransform implements TransformAction { @InputArtifact abstract File getInput() @@ -173,7 +168,7 @@ allprojects { allprojects { dependencies { //Multi step transform, without dependencies at step 1 - registerTransformAction(SimpleTransform) { + registerTransform(SimpleTransform) { from.attribute(color, 'blue') to.attribute(color, 'yellow') } @@ -413,14 +408,14 @@ project(':common') { buildFile << """ allprojects { dependencies { - registerTransformAction(NoneTransformAction) { + registerTransform(NoneTransform) { from.attribute(color, 'blue') to.attribute(color, 'green') } } } -abstract class NoneTransformAction implements TransformAction { +abstract class NoneTransform implements TransformAction { @InputArtifactDependencies @PathSensitive(PathSensitivity.NONE) abstract FileCollection getInputArtifactDependencies() @@ -509,14 +504,14 @@ abstract class NoneTransformAction implements TransformAction { buildFile << """ allprojects { dependencies { - registerTransformAction(ClasspathTransformAction) { + registerTransform(ClasspathTransform) { from.attribute(color, 'blue') to.attribute(color, 'green') } } } -abstract class ClasspathTransformAction implements TransformAction { +abstract class ClasspathTransform implements TransformAction { @InputArtifactDependencies @${classpathAnnotation.simpleName} abstract FileCollection getInputArtifactDependencies() diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformWithFileInputsIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformWithFileInputsIntegrationTest.groovy index 3b2ecdc63ee10..a042e9de43a65 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformWithFileInputsIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ArtifactTransformWithFileInputsIntegrationTest.groovy @@ -18,7 +18,6 @@ package org.gradle.integtests.resolve.transform import org.gradle.api.tasks.PathSensitivity import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest -import org.gradle.util.ToBeImplemented import spock.lang.Unroll class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyResolutionTest implements ArtifactTransformTestFixture { @@ -32,28 +31,26 @@ class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyR """) } buildFile << """ - @AssociatedTransformAction(MakeGreenAction) - interface MakeGreen { - $inputAnnotations - ConfigurableFileCollection getSomeFiles() - } + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters{ + $inputAnnotations + ConfigurableFileCollection getSomeFiles() + } - abstract class MakeGreenAction implements TransformAction { - @TransformParameters - abstract MakeGreen getParameters() @InputArtifact abstract File getInput() void transform(TransformOutputs outputs) { println "processing \${input.name} using \${parameters.someFiles*.name}" def output = outputs.file(input.name + ".green") - output.text = "ok" + def paramContent = parameters.someFiles.collect { it.file ? it.text : it.list().length }.join("") + output.text = input.text + paramContent + ".green" } } """ } - def "transform can receive pre-built file collection via parameter object"() { + def "transform can receive a file collection containing pre-built files via parameter object"() { settingsFile << """ include 'a', 'b', 'c' """ @@ -70,6 +67,8 @@ class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyR } } """ + file('a/a.txt').text = '123' + file('a/b.txt').text = 'abc' when: run(":a:resolve") @@ -134,7 +133,7 @@ class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyR implementation project(':c') } } -""" + """ when: run(":a:resolve") @@ -152,7 +151,7 @@ class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyR """ setupBuildWithTransformFileInputs() buildFile << """ - abstract class MakeRedAction implements TransformAction { + abstract class MakeRed implements TransformAction { @InputArtifact abstract File getInput() @@ -173,7 +172,7 @@ class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyR attributes.attribute(attr, 'red') }.files dependencies { - registerTransformAction(MakeRedAction) { + registerTransform(MakeRed) { from.attribute(color, 'blue') to.attribute(color, 'red') } @@ -257,6 +256,108 @@ class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyR outputContains("result = [b.jar.green, c.jar.green]") } + def "transform can receive a task output file as parameter"() { + settingsFile << """ + include 'a', 'b', 'c', 'd', 'e' + """ + buildFile << """ + allprojects { + task tool(type: FileProducer) { + output = file("build/tool-\${project.name}.jar") + } + ext.inputFile = tool.output + } + """ + setupBuildWithColorTransform { + params(""" + someFile.set(project.inputFile) + """) + } + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + implementation project(':c') + } + } + + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + @InputFile + RegularFileProperty getSomeFile() + } + + @InputArtifact + abstract File getInput() + + void transform(TransformOutputs outputs) { + println "processing \${input.name} using \${parameters.someFile.get().asFile.name}" + def output = outputs.file(input.name + ".green") + output.text = input.text + parameters.someFile.get().asFile.text + ".green" + } + } + """ + + when: + run(":a:resolve") + + then: + result.assertTasksExecuted(":a:tool", ":b:producer", ":c:producer", ":a:resolve") + outputContains("processing b.jar using tool-a.jar") + outputContains("processing c.jar using tool-a.jar") + } + + def "transform can receive a task output directory as parameter"() { + settingsFile << """ + include 'a', 'b', 'c', 'd', 'e' + """ + buildFile << """ + allprojects { + task tool(type: DirProducer) { + output = file("build/tool-\${project.name}-dir") + } + ext.inputDir = tool.output + } + """ + setupBuildWithColorTransform { + params(""" + someDir.set(project.inputDir) + """) + } + buildFile << """ + project(':a') { + dependencies { + implementation project(':b') + implementation project(':c') + } + } + + abstract class MakeGreen implements TransformAction { + interface Parameters extends TransformParameters { + @InputDirectory + DirectoryProperty getSomeDir() + } + + @InputArtifact + abstract File getInput() + + void transform(TransformOutputs outputs) { + println "processing \${input.name} using \${parameters.someDir.get().asFile.name}" + def output = outputs.file(input.name + ".green") + output.text = input.text + parameters.someDir.get().asFile.list().length + ".green" + } + } + """ + + when: + run(":a:resolve") + + then: + result.assertTasksExecuted(":a:tool", ":b:producer", ":c:producer", ":a:resolve") + outputContains("processing b.jar using tool-a-dir") + outputContains("processing c.jar using tool-a-dir") + } + def "transform does not execute when file inputs cannot be built"() { settingsFile << """ include 'a', 'b', 'c' @@ -346,7 +447,6 @@ class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyR PathSensitivity.ABSOLUTE | [['first/input', 'foo'], ['first/input', 'foo'], ['third/input', 'foo']] } - @ToBeImplemented def "can use classpath normalization for parameter object"() { settingsFile << """ include 'a', 'b', 'c' @@ -392,11 +492,7 @@ class ArtifactTransformWithFileInputsIntegrationTest extends AbstractDependencyR jarSources.zipTo(inputJar) run(":a:resolve") then: - // TODO: Should not report changes - // outputDoesNotContain("Transform artifact") - outputContains("processing b.jar using [${inputDir.name}, ${inputJar.name}]") - outputContains("processing c.jar using [${inputDir.name}, ${inputJar.name}]") - + outputDoesNotContain("processing") outputContains("result = [b.jar.green, c.jar.green]") when: diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ConcurrentBuildsArtifactTransformIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ConcurrentBuildsArtifactTransformIntegrationTest.groovy index fe6de50a6fbce..a60aa6b3fa528 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ConcurrentBuildsArtifactTransformIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/ConcurrentBuildsArtifactTransformIntegrationTest.groovy @@ -17,21 +17,14 @@ package org.gradle.integtests.resolve.transform import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest -import org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner import org.gradle.test.fixtures.server.http.BlockingHttpServer import org.junit.Rule -import org.junit.runner.RunWith -import static org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner.configureIncrementalArtifactTransformations - -@RunWith(ExperimentalIncrementalArtifactTransformationsRunner) class ConcurrentBuildsArtifactTransformIntegrationTest extends AbstractDependencyResolutionTest { @Rule BlockingHttpServer server = new BlockingHttpServer() def setup() { server.start() - configureIncrementalArtifactTransformations(settingsFile) - buildFile << """ enum Color { Red, Green, Blue } def type = Attribute.of("artifactType", String) diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/CrashingBuildsArtifactTransformIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/CrashingBuildsArtifactTransformIntegrationTest.groovy index 2035482f5cd8e..b00a30e4bc7e7 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/CrashingBuildsArtifactTransformIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/CrashingBuildsArtifactTransformIntegrationTest.groovy @@ -17,16 +17,10 @@ package org.gradle.integtests.resolve.transform import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest -import org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner -import org.junit.runner.RunWith -import static org.gradle.integtests.fixtures.ExperimentalIncrementalArtifactTransformationsRunner.configureIncrementalArtifactTransformations - -@RunWith(ExperimentalIncrementalArtifactTransformationsRunner) class CrashingBuildsArtifactTransformIntegrationTest extends AbstractDependencyResolutionTest { def "cleans up cached output after build process crashes during transform"() { given: - configureIncrementalArtifactTransformations(settingsFile) buildFile << """ enum Color { Red, Green, Blue } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/DisambiguateArtifactTransformIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/DisambiguateArtifactTransformIntegrationTest.groovy index 2f0fb0d007c5a..b3ba500823f0f 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/DisambiguateArtifactTransformIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/DisambiguateArtifactTransformIntegrationTest.groovy @@ -18,6 +18,7 @@ package org.gradle.integtests.resolve.transform import org.gradle.integtests.fixtures.AbstractHttpDependencyResolutionTest import spock.lang.Issue +import spock.lang.Unroll class DisambiguateArtifactTransformIntegrationTest extends AbstractHttpDependencyResolutionTest { @@ -168,7 +169,9 @@ project(':app') { dependencies { registerTransform { + from.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API_CLASSES)) from.attribute(artifactType, 'java-classes-directory') + to.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API)) to.attribute(artifactType, 'final') if (project.hasProperty('extraAttribute')) { @@ -179,7 +182,9 @@ project(':app') { artifactTransform(TestTransform) } registerTransform { + from.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API_JARS)) from.attribute(artifactType, 'jar') + to.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API)) to.attribute(artifactType, 'magic-jar') if (project.hasProperty('extraAttribute')) { @@ -479,4 +484,124 @@ task resolve(type: Copy) { output.count('minified=true') output.count('Sizing') == 0 } + + @Unroll + def "disambiguation leverages schema rules before doing it size based"() { + given: + settingsFile << """ +include('child') +""" + buildFile << """ +def artifactType = Attribute.of('artifactType', String) + +apply plugin: 'java-library' + +allprojects { + repositories { + maven { url "${mavenRepo.uri}" } + } +} + +project(':child') { + configurations { + runtimeElements { + canBeResolved = false + attributes { + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL)) + } + } + } + + + artifacts { + buildDir.mkdirs() + file("\$buildDir/test.jar").text = "toto" + runtimeElements file("\$buildDir/test.jar") + } +} + +dependencies { + api project(':child') + + artifactTypes.getByName("jar") { + attributes.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, "weird")) + } + + if ($apiFirst) { + registerTransform { + from.attribute(artifactType, 'jar') + from.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, "weird")) + to.attribute(artifactType, 'jar') + to.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API)) + artifactTransform(Identity) + } + } + registerTransform { + from.attribute(artifactType, 'jar') + from.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, "weird")) + to.attribute(artifactType, 'jar') + to.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + artifactTransform(IllegalTransform) + } + if (!$apiFirst) { + registerTransform { + from.attribute(artifactType, 'jar') + from.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, "weird")) + to.attribute(artifactType, 'jar') + to.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_API)) + artifactTransform(Identity) + } + } + registerTransform { + from.attribute(artifactType, 'jar') + to.attribute(artifactType, 'size') + artifactTransform(FileSizer) + } +} + +class Identity extends ArtifactTransform { + List transform(File input) { + return [input] + } +} + +class IllegalTransform extends ArtifactTransform { + List transform(File input) { + throw new IllegalStateException("IllegalTransform should not be invoked") + } +} + +class FileSizer extends ArtifactTransform { + List transform(File input) { + assert outputDirectory.directory && outputDirectory.list().length == 0 + def output = new File(outputDirectory, input.name + ".txt") + println "Sizing \${input.name} to \${output.name}" + output.text = String.valueOf(input.length()) + return [output] + } +} + +task resolve(type: Copy) { + def artifacts = configurations.compileClasspath.incoming.artifactView { + attributes { it.attribute(artifactType, 'size') } + }.artifacts + from artifacts.artifactFiles + into "\${buildDir}/libs" + doLast { + println "files: " + artifacts.collect { it.file.name } + println "ids: " + artifacts.collect { it.id } + println "components: " + artifacts.collect { it.id.componentIdentifier } + println "variants: " + artifacts.collect { it.variant.attributes } + } +} +""" + when: + succeeds "resolve" + + then: + output.contains('variants: [{artifactType=size, org.gradle.dependency.bundling=external, org.gradle.usage=java-api}]') + + where: + apiFirst << [true, false] + } } diff --git a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/TransformationLoggingIntegrationTest.groovy b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/TransformationLoggingIntegrationTest.groovy index ee53c80837116..b861deec58033 100644 --- a/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/TransformationLoggingIntegrationTest.groovy +++ b/subprojects/dependency-management/src/integTest/groovy/org/gradle/integtests/resolve/transform/TransformationLoggingIntegrationTest.groovy @@ -20,12 +20,10 @@ import org.gradle.api.logging.configuration.ConsoleOutput import org.gradle.integtests.fixtures.console.AbstractConsoleGroupedTaskFunctionalTest import spock.lang.Unroll -import static org.gradle.integtests.fixtures.FeaturePreviewsFixture.enableIncrementalArtifactTransformations - class TransformationLoggingIntegrationTest extends AbstractConsoleGroupedTaskFunctionalTest { ConsoleOutput consoleType - private static final List TESTED_CONSOLE_TYPES = [ConsoleOutput.Plain, ConsoleOutput.Verbose, ConsoleOutput.Rich] + private static final List TESTED_CONSOLE_TYPES = [ConsoleOutput.Plain, ConsoleOutput.Verbose, ConsoleOutput.Rich, ConsoleOutput.Auto] def setup() { settingsFile << """ @@ -34,7 +32,6 @@ class TransformationLoggingIntegrationTest extends AbstractConsoleGroupedTaskFun include 'util' include 'app' """ - enableIncrementalArtifactTransformations(settingsFile) buildFile << """ import java.nio.file.Files diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java index 6d43eafc6760c..4c503fcb0fa1c 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DefaultDependencyManagementServices.java @@ -15,9 +15,12 @@ */ package org.gradle.api.internal.artifacts; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; import org.gradle.StartParameter; import org.gradle.api.Describable; import org.gradle.api.artifacts.ConfigurablePublishArtifact; +import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; import org.gradle.api.artifacts.dsl.ArtifactHandler; import org.gradle.api.artifacts.dsl.ComponentMetadataHandler; import org.gradle.api.artifacts.dsl.ComponentModuleMetadataHandler; @@ -53,11 +56,12 @@ import org.gradle.api.internal.artifacts.ivyservice.ShortCircuitEmptyConfigurationResolver; import org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution.DependencySubstitutionRules; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ResolveIvyFactory; +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradlePomModuleDescriptorParser; -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme; import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.LocalComponentMetadataBuilder; import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.LocalConfigurationMetadataBuilder; +import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvedArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.result.AttributeContainerSerializer; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.store.ResolutionResultsStoreFactory; import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator; @@ -76,9 +80,13 @@ import org.gradle.api.internal.artifacts.transform.DefaultTransformerInvoker; import org.gradle.api.internal.artifacts.transform.DefaultVariantTransformRegistry; import org.gradle.api.internal.artifacts.transform.DomainObjectProjectStateHandler; +import org.gradle.api.internal.artifacts.transform.ExecutionGraphDependenciesResolver; import org.gradle.api.internal.artifacts.transform.ImmutableCachingTransformationWorkspaceProvider; import org.gradle.api.internal.artifacts.transform.MutableCachingTransformationWorkspaceProvider; import org.gradle.api.internal.artifacts.transform.MutableTransformationWorkspaceProvider; +import org.gradle.api.internal.artifacts.transform.Transformation; +import org.gradle.api.internal.artifacts.transform.TransformationNode; +import org.gradle.api.internal.artifacts.transform.TransformationNodeRegistry; import org.gradle.api.internal.artifacts.transform.TransformationRegistrationFactory; import org.gradle.api.internal.artifacts.transform.TransformerInvoker; import org.gradle.api.internal.artifacts.type.ArtifactTypeRegistry; @@ -95,8 +103,10 @@ import org.gradle.api.internal.project.ProjectStateRegistry; import org.gradle.api.internal.tasks.TaskResolver; import org.gradle.api.model.ObjectFactory; +import org.gradle.caching.internal.origin.OriginMetadata; import org.gradle.configuration.internal.UserCodeApplicationContext; import org.gradle.initialization.ProjectAccessListener; +import org.gradle.internal.Try; import org.gradle.internal.authentication.AuthenticationSchemeRegistry; import org.gradle.internal.build.BuildState; import org.gradle.internal.classloader.ClassLoaderHierarchyHasher; @@ -106,24 +116,32 @@ import org.gradle.internal.component.external.model.ModuleComponentArtifactMetadata; import org.gradle.internal.component.model.ComponentAttributeMatcher; import org.gradle.internal.event.ListenerManager; +import org.gradle.internal.execution.CachingContext; +import org.gradle.internal.execution.CachingResult; +import org.gradle.internal.execution.ExecutionOutcome; +import org.gradle.internal.execution.IncrementalContext; import org.gradle.internal.execution.OutputChangeListener; -import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; +import org.gradle.internal.execution.UnitOfWork; +import org.gradle.internal.execution.UpToDateResult; import org.gradle.internal.execution.WorkExecutor; +import org.gradle.internal.execution.caching.CachingState; +import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import org.gradle.internal.execution.history.BeforeExecutionState; import org.gradle.internal.execution.history.ExecutionHistoryStore; -import org.gradle.internal.execution.history.OutputFilesRepository; +import org.gradle.internal.execution.history.changes.ExecutionStateChangeDetector; import org.gradle.internal.execution.impl.DefaultWorkExecutor; -import org.gradle.internal.execution.impl.steps.CatchExceptionStep; -import org.gradle.internal.execution.impl.steps.Context; -import org.gradle.internal.execution.impl.steps.CreateOutputsStep; -import org.gradle.internal.execution.impl.steps.CurrentSnapshotResult; -import org.gradle.internal.execution.impl.steps.ExecuteStep; -import org.gradle.internal.execution.impl.steps.PrepareCachingStep; -import org.gradle.internal.execution.impl.steps.SkipUpToDateStep; -import org.gradle.internal.execution.impl.steps.SnapshotOutputStep; -import org.gradle.internal.execution.impl.steps.StoreSnapshotsStep; -import org.gradle.internal.execution.impl.steps.TimeoutStep; -import org.gradle.internal.execution.impl.steps.UpToDateResult; +import org.gradle.internal.execution.steps.BroadcastChangingOutputsStep; +import org.gradle.internal.execution.steps.CatchExceptionStep; +import org.gradle.internal.execution.steps.CreateOutputsStep; +import org.gradle.internal.execution.steps.ExecuteStep; +import org.gradle.internal.execution.steps.ResolveChangesStep; +import org.gradle.internal.execution.steps.SkipUpToDateStep; +import org.gradle.internal.execution.steps.SnapshotOutputsStep; +import org.gradle.internal.execution.steps.StoreSnapshotsStep; +import org.gradle.internal.execution.steps.TimeoutStep; import org.gradle.internal.execution.timeout.TimeoutHandler; +import org.gradle.internal.fingerprint.FileCollectionFingerprint; import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry; import org.gradle.internal.fingerprint.impl.OutputFileCollectionFingerprinter; import org.gradle.internal.id.UniqueId; @@ -141,7 +159,6 @@ import org.gradle.internal.service.DefaultServiceRegistry; import org.gradle.internal.service.ServiceRegistration; import org.gradle.internal.service.ServiceRegistry; -import org.gradle.internal.snapshot.FileSystemSnapshot; import org.gradle.internal.snapshot.FileSystemSnapshotter; import org.gradle.internal.snapshot.ValueSnapshotter; import org.gradle.internal.typeconversion.NotationParser; @@ -149,8 +166,9 @@ import org.gradle.vcs.internal.VcsMappingsStore; import javax.annotation.Nullable; -import java.io.File; +import java.util.Collection; import java.util.List; +import java.util.Optional; public class DefaultDependencyManagementServices implements DependencyManagementServices { @@ -193,36 +211,46 @@ OutputFileCollectionFingerprinter createOutputFingerprinter(FileSystemSnapshotte return new OutputFileCollectionFingerprinter(fileSystemSnapshotter); } + TransformationNodeRegistry createTransformationNodeRegistry() { + return new TransformationNodeRegistry() { + @Override + public Collection getOrCreate(ResolvedArtifactSet artifactSet, Transformation transformation, ExecutionGraphDependenciesResolver dependenciesResolver) { + throw new UnsupportedOperationException("Cannot schedule transforms for build script dependencies"); + } + + @Override + public Optional getCompleted(ComponentArtifactIdentifier artifactId, Transformation transformation) { + return Optional.empty(); + } + }; + } + /** * Work executer for usage above Gradle scope * * Currently used for running artifact transformations in buildscript blocks. */ - WorkExecutor createWorkExecutor( - TimeoutHandler timeoutHandler, ListenerManager listenerManager + WorkExecutor createWorkExecutor( + ExecutionStateChangeDetector changeDetector, + ListenerManager listenerManager, + TimeoutHandler timeoutHandler ) { OutputChangeListener outputChangeListener = listenerManager.getBroadcaster(OutputChangeListener.class); - OutputFilesRepository noopOutputFilesRepository = new OutputFilesRepository() { - @Override - public boolean isGeneratedByGradle(File file) { - return true; - } - - @Override - public void recordOutputs(Iterable outputFileFingerprints) { - } - }; // TODO: Figure out how to get rid of origin scope id in snapshot outputs step UniqueId fixedUniqueId = UniqueId.from("dhwwyv4tqrd43cbxmdsf24wquu"); - return new DefaultWorkExecutor( - new SkipUpToDateStep( - new StoreSnapshotsStep(noopOutputFilesRepository, - new PrepareCachingStep( - new SnapshotOutputStep(fixedUniqueId, - new CreateOutputsStep( - new CatchExceptionStep( - new TimeoutStep(timeoutHandler, - new ExecuteStep(outputChangeListener) + return new DefaultWorkExecutor<>( + new NoOpCachingStateStep( + new ResolveChangesStep<>(changeDetector, + new SkipUpToDateStep<>( + new BroadcastChangingOutputsStep<>(outputChangeListener, + new StoreSnapshotsStep<>( + new SnapshotOutputsStep<>(fixedUniqueId, + new CreateOutputsStep<>( + new CatchExceptionStep<>( + new TimeoutStep<>(timeoutHandler, + new ExecuteStep<>() + ) + ) ) ) ) @@ -234,6 +262,75 @@ public void recordOutputs(Iterable outputFileFinge } } + private static class NoOpCachingStateStep implements Step { + private final Step delegate; + + public NoOpCachingStateStep(Step delegate) { + this.delegate = delegate; + } + + @Override + public CachingResult execute(IncrementalContext context) { + UpToDateResult result = delegate.execute(new CachingContext() { + @Override + public CachingState getCachingState() { + return CachingState.NOT_DETERMINED; + } + + @Override + public Optional getRebuildReason() { + return context.getRebuildReason(); + } + + @Override + public Optional getAfterPreviousExecutionState() { + return context.getAfterPreviousExecutionState(); + } + + @Override + public Optional getBeforeExecutionState() { + return context.getBeforeExecutionState(); + } + + @Override + public UnitOfWork getWork() { + return context.getWork(); + } + }); + return new CachingResult() { + @Override + public CachingState getCachingState() { + return CachingState.NOT_DETERMINED; + } + + @Override + public ImmutableList getExecutionReasons() { + return result.getExecutionReasons(); + } + + @Override + public ImmutableSortedMap getFinalOutputs() { + return result.getFinalOutputs(); + } + + @Override + public OriginMetadata getOriginMetadata() { + return result.getOriginMetadata(); + } + + @Override + public boolean isReused() { + return result.isReused(); + } + + @Override + public Try getOutcome() { + return result.getOutcome(); + } + }; + } + } + private static class DependencyResolutionScopeServices { private final DomainObjectContext domainObjectContext; @@ -254,25 +351,22 @@ MutableCachingTransformationWorkspaceProvider createCachingTransformerWorkspaceP return new MutableCachingTransformationWorkspaceProvider(workspaceProvider); } - TransformerInvoker createTransformerInvoker(WorkExecutor workExecutor, + TransformerInvoker createTransformerInvoker(WorkExecutor workExecutor, FileSystemSnapshotter fileSystemSnapshotter, ImmutableCachingTransformationWorkspaceProvider transformationWorkspaceProvider, ArtifactTransformListener artifactTransformListener, - FileCollectionFingerprinterRegistry fileCollectionFingerprinterRegistry, FileCollectionFactory fileCollectionFactory, ClassLoaderHierarchyHasher classLoaderHierarchyHasher, - ProjectFinder projectFinder, - FeaturePreviews featurePreviews) { + ProjectFinder projectFinder + ) { return new DefaultTransformerInvoker( workExecutor, fileSystemSnapshotter, artifactTransformListener, transformationWorkspaceProvider, - fileCollectionFingerprinterRegistry, fileCollectionFactory, classLoaderHierarchyHasher, - projectFinder, - featurePreviews.isFeatureEnabled(FeaturePreviews.Feature.INCREMENTAL_ARTIFACT_TRANSFORMATIONS) + projectFinder ); } @@ -333,7 +427,7 @@ BaseRepositoryFactory createBaseRepositoryFactory(LocalMavenRepositoryLocator lo artifactIdentifierFileStore, externalResourceFileStore, new GradlePomModuleDescriptorParser(versionSelectorScheme, moduleIdentifierFactory, fileResourceRepository, metadataFactory), - new ModuleMetadataParser(attributesFactory, moduleIdentifierFactory, NamedObjectInstantiator.INSTANCE), + new GradleModuleMetadataParser(attributesFactory, moduleIdentifierFactory, NamedObjectInstantiator.INSTANCE), authenticationSchemeRegistry, ivyContextManager, moduleIdentifierFactory, @@ -412,7 +506,8 @@ DependencyHandler createDependencyHandler(Instantiator instantiator, Configurati resolutionQueryFactory, attributesSchema, artifactTransformRegistrations, - artifactTypeRegistry); + artifactTypeRegistry, + NamedObjectInstantiator.INSTANCE); } DependencyLockingHandler createDependencyLockingHandler(Instantiator instantiator, ConfigurationContainerInternal configurationContainer) { @@ -424,7 +519,7 @@ DependencyLockingProvider createDependencyLockingProvider(Instantiator instantia } DependencyConstraintHandler createDependencyConstraintHandler(Instantiator instantiator, ConfigurationContainerInternal configurationContainer, DependencyFactory dependencyFactory, ComponentMetadataHandler componentMetadataHandler) { - return instantiator.newInstance(DefaultDependencyConstraintHandler.class, configurationContainer, dependencyFactory, componentMetadataHandler); + return instantiator.newInstance(DefaultDependencyConstraintHandler.class, configurationContainer, dependencyFactory, NamedObjectInstantiator.INSTANCE); } DefaultComponentMetadataHandler createComponentMetadataHandler(Instantiator instantiator, ImmutableModuleIdentifierFactory moduleIdentifierFactory, SimpleMapInterner interner, ImmutableAttributesFactory attributesFactory, IsolatableFactory isolatableFactory, ComponentMetadataRuleExecutor componentMetadataRuleExecutor) { @@ -458,7 +553,8 @@ ConfigurationResolver createDependencyResolver(ArtifactDependencyResolver artifa ArtifactTypeRegistry artifactTypeRegistry, ComponentSelectorConverter componentSelectorConverter, AttributeContainerSerializer attributeContainerSerializer, - BuildState currentBuild) { + BuildState currentBuild, + TransformationNodeRegistry transformationNodeRegistry) { return new ErrorHandlingConfigurationResolver( new ShortCircuitEmptyConfigurationResolver( new DefaultConfigurationResolver( @@ -474,7 +570,8 @@ ConfigurationResolver createDependencyResolver(ArtifactDependencyResolver artifa attributesSchema, attributesFactory), attributesSchema, - attributesFactory + attributesFactory, + transformationNodeRegistry ), moduleIdentifierFactory, buildOperationExecutor, diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java index 539373755ba7e..f5ade7a8e7b18 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyManagementGlobalScopeServices.java @@ -19,7 +19,6 @@ import com.google.common.collect.ImmutableSet; import org.gradle.api.artifacts.transform.InputArtifact; import org.gradle.api.artifacts.transform.InputArtifactDependencies; -import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.api.internal.artifacts.ivyservice.DefaultIvyContextManager; import org.gradle.api.internal.artifacts.ivyservice.IvyContextManager; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionComparator; @@ -36,12 +35,12 @@ import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ProjectIvyDependencyDescriptorFactory; import org.gradle.api.internal.artifacts.transform.ArtifactTransformActionScheme; import org.gradle.api.internal.artifacts.transform.ArtifactTransformParameterScheme; +import org.gradle.api.internal.artifacts.transform.CacheableTransformTypeAnnotationHandler; import org.gradle.api.internal.artifacts.transform.InputArtifactAnnotationHandler; import org.gradle.api.internal.artifacts.transform.InputArtifactDependenciesAnnotationHandler; import org.gradle.api.internal.tasks.properties.InspectionScheme; import org.gradle.api.internal.tasks.properties.InspectionSchemeFactory; -import org.gradle.api.internal.tasks.properties.annotations.NoOpPropertyAnnotationHandler; -import org.gradle.api.internal.tasks.properties.annotations.PropertyAnnotationHandler; +import org.gradle.api.internal.tasks.properties.annotations.TypeAnnotationHandler; import org.gradle.api.tasks.Classpath; import org.gradle.api.tasks.CompileClasspath; import org.gradle.api.tasks.Console; @@ -50,10 +49,7 @@ import org.gradle.api.tasks.InputFile; import org.gradle.api.tasks.InputFiles; import org.gradle.api.tasks.Internal; -import org.gradle.api.tasks.Nested; import org.gradle.cache.internal.ProducerGuard; -import org.gradle.internal.instantiation.DefaultInjectAnnotationHandler; -import org.gradle.internal.instantiation.InjectAnnotationHandler; import org.gradle.internal.instantiation.InstantiationScheme; import org.gradle.internal.instantiation.InstantiatorFactory; import org.gradle.internal.nativeplatform.filesystem.FileSystem; @@ -63,8 +59,6 @@ import org.gradle.internal.resource.local.FileResourceRepository; import org.gradle.internal.resource.transport.file.FileConnectorFactory; -import javax.inject.Inject; - class DependencyManagementGlobalScopeServices { FileResourceRepository createFileResourceRepository(FileSystem fileSystem) { return new FileResourceConnector(fileSystem); @@ -113,6 +107,10 @@ ProducerGuard createProducerAccess() { return ProducerGuard.adaptive(); } + TypeAnnotationHandler createCacheableTransformAnnotationHandler() { + return new CacheableTransformTypeAnnotationHandler(); + } + InputArtifactAnnotationHandler createInputArtifactAnnotationHandler() { return new InputArtifactAnnotationHandler(); } @@ -121,25 +119,17 @@ InputArtifactDependenciesAnnotationHandler createInputArtifactDependenciesAnnota return new InputArtifactDependenciesAnnotationHandler(); } - InjectAnnotationHandler createTransformParametersAnnotationHandler() { - return new DefaultInjectAnnotationHandler(TransformParameters.class); - } - - PropertyAnnotationHandler createTransformParametersPropertyAnnotationHandler() { - return new NoOpPropertyAnnotationHandler(TransformParameters.class); - } - ArtifactTransformParameterScheme createArtifactTransformParameterScheme(InspectionSchemeFactory inspectionSchemeFactory, InstantiatorFactory instantiatorFactory) { // TODO - should decorate InstantiationScheme instantiationScheme = instantiatorFactory.injectScheme(); - InspectionScheme inspectionScheme = inspectionSchemeFactory.inspectionScheme(ImmutableSet.of(Input.class, InputFile.class, InputFiles.class, InputDirectory.class, Classpath.class, CompileClasspath.class, Nested.class, Inject.class, Console.class, Internal.class)); + InspectionScheme inspectionScheme = inspectionSchemeFactory.inspectionScheme(ImmutableSet.of(Input.class, InputFile.class, InputFiles.class, InputDirectory.class, Classpath.class, CompileClasspath.class, Console.class, Internal.class), instantiationScheme); return new ArtifactTransformParameterScheme(instantiationScheme, inspectionScheme); } ArtifactTransformActionScheme createArtifactTransformActionScheme(InspectionSchemeFactory inspectionSchemeFactory, InstantiatorFactory instantiatorFactory) { - InstantiationScheme instantiationScheme = instantiatorFactory.injectScheme(ImmutableSet.of(InputArtifact.class, InputArtifactDependencies.class, TransformParameters.class)); + InstantiationScheme instantiationScheme = instantiatorFactory.injectScheme(ImmutableSet.of(InputArtifact.class, InputArtifactDependencies.class)); + InspectionScheme inspectionScheme = inspectionSchemeFactory.inspectionScheme(ImmutableSet.of(InputArtifact.class, InputArtifactDependencies.class, Classpath.class, CompileClasspath.class), instantiationScheme); InstantiationScheme legacyInstantiationScheme = instantiatorFactory.injectScheme(); - InspectionScheme inspectionScheme = inspectionSchemeFactory.inspectionScheme(ImmutableSet.of(InputArtifact.class, InputArtifactDependencies.class, TransformParameters.class, Inject.class, Classpath.class, CompileClasspath.class)); return new ArtifactTransformActionScheme(instantiationScheme, inspectionScheme, legacyInstantiationScheme); } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyServices.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyServices.java index 9043e0c203fc9..b50eb54d8e474 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyServices.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/DependencyServices.java @@ -17,10 +17,10 @@ package org.gradle.api.internal.artifacts; import org.gradle.api.internal.artifacts.transform.ArtifactTransformListener; -import org.gradle.api.internal.artifacts.transform.DefaultTransformationNodeFactory; +import org.gradle.api.internal.artifacts.transform.DefaultTransformationNodeRegistry; import org.gradle.api.internal.artifacts.transform.TransformationNodeDependencyResolver; import org.gradle.api.internal.artifacts.transform.TransformationNodeExecutor; -import org.gradle.api.internal.artifacts.transform.TransformationNodeFactory; +import org.gradle.api.internal.artifacts.transform.TransformationNodeRegistry; import org.gradle.internal.event.ListenerManager; import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.service.ServiceRegistration; @@ -56,12 +56,12 @@ ArtifactTransformListener createArtifactTransformListener(ListenerManager listen return listenerManager.getBroadcaster(ArtifactTransformListener.class); } - TransformationNodeFactory createTransformationNodeFactory() { - return new DefaultTransformationNodeFactory(); + TransformationNodeRegistry createTransformationNodeRegistry() { + return new DefaultTransformationNodeRegistry(); } - TransformationNodeDependencyResolver createTransformationNodeDependencyResolver(TransformationNodeFactory transformationNodeFactory) { - return new TransformationNodeDependencyResolver(transformationNodeFactory); + TransformationNodeDependencyResolver createTransformationNodeDependencyResolver(TransformationNodeRegistry transformationNodeRegistry) { + return new TransformationNodeDependencyResolver(transformationNodeRegistry); } TransformationNodeExecutor createTransformationNodeExecutor(BuildOperationExecutor buildOperationExecutor, ArtifactTransformListener transformListener) { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolverResults.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolverResults.java index 99ad65411eefd..00950631f622a 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolverResults.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ResolverResults.java @@ -21,6 +21,8 @@ import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.VisitedArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.projectresult.ResolvedLocalComponentsResult; +import javax.annotation.Nullable; + public interface ResolverResults { boolean hasError(); @@ -71,6 +73,7 @@ public interface ResolverResults { * removes the exception from the underlying resolver results, meaning that subsequent calls to consume * will return null. */ + @Nullable ResolveException consumeNonFatalFailure(); /** @@ -79,6 +82,7 @@ public interface ResolverResults { * this doesn't consume the error, so subsequent calls will return the same instance, unless the error was * consumed in between. */ + @Nullable Throwable getFailure(); boolean hasResolutionResult(); diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/VariantTransformRegistry.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/VariantTransformRegistry.java index 4db06e169b3f5..50fcb3e30b9a0 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/VariantTransformRegistry.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/VariantTransformRegistry.java @@ -17,8 +17,8 @@ package org.gradle.api.internal.artifacts; import org.gradle.api.Action; -import org.gradle.api.artifacts.transform.ParameterizedTransformSpec; import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.api.artifacts.transform.TransformSpec; import org.gradle.api.artifacts.transform.VariantTransform; @@ -31,9 +31,7 @@ public interface VariantTransformRegistry { */ void registerTransform(Action registrationAction); - void registerTransform(Class parameterType, Action> registrationAction); - - void registerTransformAction(Class actionType, Action registrationAction); + void registerTransform(Class> actionType, Action> registrationAction); Iterable getTransforms(); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java index 0901dff0d1a19..0558a73d74146 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ConfigurationInternal.java @@ -15,6 +15,7 @@ */ package org.gradle.api.internal.artifacts.configurations; +import org.gradle.api.Action; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ExcludeRule; import org.gradle.api.internal.artifacts.ResolveContext; @@ -59,6 +60,11 @@ enum InternalState { */ OutgoingVariant convertToOutgoingVariant(); + /** + * Registers an action to execute before locking for further mutation. + */ + void beforeLocking(Action action); + void preventFromFurtherMutation(); /** diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java index b7bfb5c149aed..7a9d833b63776 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfiguration.java @@ -96,6 +96,7 @@ import org.gradle.api.tasks.TaskDependency; import org.gradle.configuration.internal.UserCodeApplicationContext; import org.gradle.initialization.ProjectAccessListener; +import org.gradle.internal.Actions; import org.gradle.internal.Cast; import org.gradle.internal.Describables; import org.gradle.internal.DisplayName; @@ -111,6 +112,7 @@ import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.RunnableBuildOperation; import org.gradle.internal.reflect.Instantiator; +import org.gradle.internal.service.ServiceRegistry; import org.gradle.internal.typeconversion.NotationParser; import org.gradle.util.CollectionUtils; import org.gradle.util.ConfigureUtil; @@ -218,6 +220,8 @@ public void validateMutation(MutationType type) { private CollectionCallbackActionDecorator callbackActionDecorator; private UserCodeApplicationContext userCodeApplicationContext; + private Action beforeLocking; + public DefaultConfiguration(DomainObjectContext domainObjectContext, String name, ConfigurationsProvider configurationsProvider, @@ -259,7 +263,7 @@ public DefaultConfiguration(DomainObjectContext domainObjectContext, this.attributesFactory = attributesFactory; this.configurationAttributes = attributesFactory.mutable(); this.domainObjectContext = domainObjectContext; - this.intrinsicFiles = new ConfigurationFileCollection(Specs.satisfyAll()); + this.intrinsicFiles = new ConfigurationFileCollection(Specs.satisfyAll()); this.documentationRegistry = documentationRegistry; this.resolutionLock = domainObjectProjectStateHandler.newExclusiveOperationLock(); this.resolvableDependencies = instantiator.newInstance(ConfigurationResolvableDependencies.class, this); @@ -387,20 +391,21 @@ public Configuration setTransitive(boolean transitive) { return this; } + @Nullable public String getDescription() { return description; } - public Configuration setDescription(String description) { + public Configuration setDescription(@Nullable String description) { this.description = description; return this; } public Set getHierarchy() { if (extendsFrom.isEmpty()) { - return Collections.singleton(this); + return Collections.singleton(this); } - Set result = WrapUtil.toLinkedSet(this); + Set result = WrapUtil.toLinkedSet(this); collectSuperConfigs(this, result); return result; } @@ -450,7 +455,7 @@ public void runDependencyActions() { } public Set getAll() { - return ImmutableSet.copyOf(configurationsProvider.getAll()); + return ImmutableSet.copyOf(configurationsProvider.getAll()); } public Set resolve() { @@ -668,7 +673,7 @@ public Project getProject() { } @Override - public void run() { + public void run(ServiceRegistry registry) { resolveExclusively(GRAPH_RESOLVED); } }, fileCollectionFactory); @@ -869,10 +874,25 @@ public OutgoingVariant convertToOutgoingVariant() { return outgoing.convertToOutgoingVariant(); } + @Override + public void beforeLocking(Action action) { + if (canBeMutated) { + if (beforeLocking != null) { + beforeLocking = Actions.composite(beforeLocking, action); + } else { + beforeLocking = action; + } + } + } + @Override public void preventFromFurtherMutation() { // TODO This should use the same `MutationValidator` infrastructure that we use for other mutation types if (canBeMutated) { + if (beforeLocking != null) { + beforeLocking.execute(this); + beforeLocking = null; + } AttributeContainerInternal delegatee = configurationAttributes.asImmutable(); configurationAttributes = new ImmutableAttributeContainerWithErrorMessage(delegatee, this.displayName); outgoing.preventFromFurtherMutation(); @@ -929,7 +949,7 @@ private DefaultConfiguration createCopy(Set dependencies, Set attribute : configurationAttributes.keySet()) { Object value = configurationAttributes.getAttribute(attribute); - copiedConfiguration.getAttributes().attribute(Cast.>uncheckedCast(attribute), value); + copiedConfiguration.getAttributes().attribute(Cast.uncheckedNonnullCast(attribute), value); } } @@ -952,11 +972,11 @@ private DefaultConfiguration createCopy(Set dependencies, SetconvertClosureToSpec(dependencySpec)); + return copy(Specs.convertClosureToSpec(dependencySpec)); } public Configuration copyRecursive(Closure dependencySpec) { - return copyRecursive(Specs.convertClosureToSpec(dependencySpec)); + return copyRecursive(Specs.convertClosureToSpec(dependencySpec)); } public ResolutionStrategyInternal getResolutionStrategy() { @@ -1297,7 +1317,7 @@ public String toString() { } public FileCollection getFiles() { - return new ConfigurationFileCollection(Specs.satisfyAll()); + return new ConfigurationFileCollection(Specs.satisfyAll()); } public DependencySet getDependencies() { @@ -1388,7 +1408,7 @@ public ArtifactCollection getArtifacts() { @Override public FileCollection getFiles() { - return new ConfigurationFileCollection(Specs.satisfyAll(), viewAttributes, componentFilter, lenient, allowNoMatchingVariants); + return new ConfigurationFileCollection(Specs.satisfyAll(), viewAttributes, componentFilter, lenient, allowNoMatchingVariants); } } @@ -1561,21 +1581,18 @@ private ImmutableAttributes lockViewAttributes() { private class ConfigurationArtifactCollection implements ArtifactCollection { private final ConfigurationFileCollection fileCollection; - private final AttributeContainerInternal viewAttributes; - private final Spec componentFilter; private final boolean lenient; private Set artifactResults; private Set failures; ConfigurationArtifactCollection() { - this(configurationAttributes, Specs.satisfyAll(), false, false); + this(configurationAttributes, Specs.satisfyAll(), false, false); } ConfigurationArtifactCollection(AttributeContainerInternal attributes, Spec componentFilter, boolean lenient, boolean allowNoMatchingVariants) { assertIsResolvable(); - this.viewAttributes = attributes.asImmutable(); - this.componentFilter = componentFilter; - this.fileCollection = new ConfigurationFileCollection(Specs.satisfyAll(), viewAttributes, this.componentFilter, lenient, allowNoMatchingVariants); + AttributeContainerInternal viewAttributes = attributes.asImmutable(); + this.fileCollection = new ConfigurationFileCollection(Specs.satisfyAll(), viewAttributes, componentFilter, lenient, allowNoMatchingVariants); this.lenient = lenient; } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationPublications.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationPublications.java index 91c35574dadae..815e93f13f965 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationPublications.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/DefaultConfigurationPublications.java @@ -106,7 +106,7 @@ public Set getArtifacts() { public Set getChildren() { PublishArtifactSet allArtifactSet = allArtifacts.getPublishArtifactSet(); LeafOutgoingVariant leafOutgoingVariant = new LeafOutgoingVariant(displayName, attributes, allArtifactSet); - if (variants == null) { + if (variants == null || variants.isEmpty()) { return Collections.singleton(leafOutgoingVariant); } boolean hasArtifacts = !allArtifactSet.isEmpty(); diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ResolveConfigurationResolutionBuildOperationDetails.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ResolveConfigurationResolutionBuildOperationDetails.java index 7947c0235cdb0..6c232b2fd2b38 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ResolveConfigurationResolutionBuildOperationDetails.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/ResolveConfigurationResolutionBuildOperationDetails.java @@ -47,9 +47,9 @@ class ResolveConfigurationResolutionBuildOperationDetails implements ResolveConf ResolveConfigurationResolutionBuildOperationDetails( String configurationName, boolean isScriptConfiguration, - String configurationDescription, + @Nullable String configurationDescription, String buildPath, - String projectPath, + @Nullable String projectPath, boolean isConfigurationVisible, boolean isConfigurationTransitive, List repositories diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/package-info.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/package-info.java new file mode 100644 index 0000000000000..88176c2b9f9b0 --- /dev/null +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/configurations/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@NonNullApi +package org.gradle.api.internal.artifacts.configurations; + +import org.gradle.api.NonNullApi; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataProcessor.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataProcessor.java index f7e79a86c91e6..af525c61d1ec7 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataProcessor.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultComponentMetadataProcessor.java @@ -181,7 +181,7 @@ public ModuleComponentResolveMetadata processMetadata(ModuleComponentResolveMeta } if (!updatedMetadata.getStatusScheme().contains(updatedMetadata.getStatus())) { - throw new ModuleVersionResolveException(updatedMetadata.getModuleVersionId(), String.format("Unexpected status '%s' specified for %s. Expected one of: %s", updatedMetadata.getStatus(), updatedMetadata.getId().getDisplayName(), updatedMetadata.getStatusScheme())); + throw new ModuleVersionResolveException(updatedMetadata.getModuleVersionId(), () -> String.format("Unexpected status '%s' specified for %s. Expected one of: %s", updatedMetadata.getStatus(), updatedMetadata.getId().getDisplayName(), updatedMetadata.getStatusScheme())); } return updatedMetadata; } @@ -201,7 +201,7 @@ public ComponentMetadata processMetadata(ComponentMetadata metadata) { updatedMetadata = details.asImmutable(); } if (!updatedMetadata.getStatusScheme().contains(updatedMetadata.getStatus())) { - throw new ModuleVersionResolveException(updatedMetadata.getId(), String.format("Unexpected status '%s' specified for %s. Expected one of: %s", updatedMetadata.getStatus(), updatedMetadata.getId().toString(), updatedMetadata.getStatusScheme())); + throw new ModuleVersionResolveException(updatedMetadata.getId(), () -> String.format("Unexpected status '%s' specified for %s. Expected one of: %s", updatedMetadata.getStatus(), updatedMetadata.getId().toString(), updatedMetadata.getStatusScheme())); } return updatedMetadata; } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java index 7a278823576fd..a0a7ac226bd1e 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/DefaultRepositoryHandler.java @@ -72,11 +72,21 @@ public FlatDirectoryArtifactRepository flatDir(Map args) { public ArtifactRepository gradlePluginPortal() { return addRepository(repositoryFactory.createGradlePluginPortal(), GRADLE_PLUGIN_PORTAL_REPO_NAME); } + + @Override + public ArtifactRepository gradlePluginPortal(Action action) { + return addRepository(repositoryFactory.createGradlePluginPortal(), GRADLE_PLUGIN_PORTAL_REPO_NAME, action); + } public MavenArtifactRepository mavenCentral() { return addRepository(repositoryFactory.createMavenCentralRepository(), DEFAULT_MAVEN_CENTRAL_REPO_NAME); } + @Override + public MavenArtifactRepository mavenCentral(Action action) { + return addRepository(repositoryFactory.createMavenCentralRepository(), DEFAULT_MAVEN_CENTRAL_REPO_NAME, action); + } + public MavenArtifactRepository jcenter() { return addRepository(repositoryFactory.createJCenterRepository(), DEFAULT_BINTRAY_JCENTER_REPO_NAME); } @@ -94,10 +104,20 @@ public MavenArtifactRepository mavenLocal() { return addRepository(repositoryFactory.createMavenLocalRepository(), DEFAULT_MAVEN_LOCAL_REPO_NAME); } + @Override + public MavenArtifactRepository mavenLocal(Action action) { + return addRepository(repositoryFactory.createMavenLocalRepository(), DEFAULT_MAVEN_LOCAL_REPO_NAME, action); + } + public MavenArtifactRepository google() { return addRepository(repositoryFactory.createGoogleRepository(), GOOGLE_REPO_NAME); } + @Override + public MavenArtifactRepository google(Action action) { + return addRepository(repositoryFactory.createGoogleRepository(), GOOGLE_REPO_NAME, action); + } + public MavenArtifactRepository maven(Action action) { return addRepository(repositoryFactory.createMavenRepository(), MAVEN_REPO_DEFAULT_NAME, action); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyConstraintHandler.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyConstraintHandler.java index acba7fdfff465..8d0d40a47363e 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyConstraintHandler.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyConstraintHandler.java @@ -21,9 +21,10 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.artifacts.DependencyConstraint; -import org.gradle.api.artifacts.dsl.ComponentMetadataHandler; import org.gradle.api.artifacts.dsl.DependencyConstraintHandler; +import org.gradle.api.attributes.Category; import org.gradle.api.internal.artifacts.dependencies.DependencyConstraintInternal; +import org.gradle.api.internal.model.NamedObjectInstantiator; import org.gradle.internal.metaobject.MethodAccess; import org.gradle.internal.metaobject.MethodMixIn; import org.gradle.util.ConfigureUtil; @@ -34,15 +35,19 @@ public class DefaultDependencyConstraintHandler implements DependencyConstraintH private final ConfigurationContainer configurationContainer; private final DependencyFactory dependencyFactory; private final DynamicAddDependencyMethods dynamicMethods; - private final ComponentMetadataHandler componentMetadataHandler; + private final NamedObjectInstantiator namedObjectInstantiator; + private final Category platform; + private final Category enforcedPlatform; public DefaultDependencyConstraintHandler(ConfigurationContainer configurationContainer, DependencyFactory dependencyFactory, - ComponentMetadataHandler componentMetadataHandler) { + NamedObjectInstantiator namedObjectInstantiator) { this.configurationContainer = configurationContainer; this.dependencyFactory = dependencyFactory; this.dynamicMethods = new DynamicAddDependencyMethods(configurationContainer, new DependencyConstraintAdder()); - this.componentMetadataHandler = componentMetadataHandler; + this.namedObjectInstantiator = namedObjectInstantiator; + platform = toCategory(Category.REGULAR_PLATFORM); + enforcedPlatform = toCategory(Category.ENFORCED_PLATFORM); } @Override @@ -68,7 +73,7 @@ public DependencyConstraint create(Object dependencyNotation, Action { @Override public DependencyConstraint add(Configuration configuration, Object dependencyNotation, Closure configureClosure) { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.java index 46e09cd54c74c..bc9d21978d095 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandler.java @@ -27,15 +27,17 @@ import org.gradle.api.artifacts.dsl.DependencyConstraintHandler; import org.gradle.api.artifacts.dsl.DependencyHandler; import org.gradle.api.artifacts.query.ArtifactResolutionQuery; -import org.gradle.api.artifacts.transform.ParameterizedTransformSpec; import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.api.artifacts.transform.TransformSpec; import org.gradle.api.artifacts.transform.VariantTransform; import org.gradle.api.artifacts.type.ArtifactTypeContainer; import org.gradle.api.attributes.AttributesSchema; +import org.gradle.api.attributes.Category; import org.gradle.api.attributes.HasConfigurableAttributes; import org.gradle.api.internal.artifacts.VariantTransformRegistry; import org.gradle.api.internal.artifacts.query.ArtifactResolutionQueryFactory; +import org.gradle.api.internal.model.NamedObjectInstantiator; import org.gradle.internal.Factory; import org.gradle.internal.metaobject.MethodAccess; import org.gradle.internal.metaobject.MethodMixIn; @@ -57,6 +59,7 @@ public class DefaultDependencyHandler implements DependencyHandler, MethodMixIn private final AttributesSchema attributesSchema; private final VariantTransformRegistry transforms; private final Factory artifactTypeContainer; + private final NamedObjectInstantiator namedObjectInstantiator; private final DynamicAddDependencyMethods dynamicMethods; public DefaultDependencyHandler(ConfigurationContainer configurationContainer, @@ -68,7 +71,8 @@ public DefaultDependencyHandler(ConfigurationContainer configurationContainer, ArtifactResolutionQueryFactory resolutionQueryFactory, AttributesSchema attributesSchema, VariantTransformRegistry transforms, - Factory artifactTypeContainer) { + Factory artifactTypeContainer, + NamedObjectInstantiator namedObjectInstantiator) { this.configurationContainer = configurationContainer; this.dependencyFactory = dependencyFactory; this.projectFinder = projectFinder; @@ -79,6 +83,7 @@ public DefaultDependencyHandler(ConfigurationContainer configurationContainer, this.attributesSchema = attributesSchema; this.transforms = transforms; this.artifactTypeContainer = artifactTypeContainer; + this.namedObjectInstantiator = namedObjectInstantiator; configureSchema(); dynamicMethods = new DynamicAddDependencyMethods(configurationContainer, new DirectDependencyAdder()); } @@ -215,20 +220,15 @@ public void registerTransform(Action registrationActio } @Override - public void registerTransform(Class parameterType, Action> registrationAction) { - transforms.registerTransform(parameterType, registrationAction); - } - - @Override - public void registerTransformAction(Class actionType, Action registrationAction) { - transforms.registerTransformAction(actionType, registrationAction); + public void registerTransform(Class> actionType, Action> registrationAction) { + transforms.registerTransform(actionType, registrationAction); } @Override public Dependency platform(Object notation) { Dependency dependency = create(notation); if (dependency instanceof HasConfigurableAttributes) { - PlatformSupport.addPlatformAttribute((HasConfigurableAttributes) dependency, PlatformSupport.REGULAR_PLATFORM); + PlatformSupport.addPlatformAttribute((HasConfigurableAttributes) dependency, toCategory(Category.REGULAR_PLATFORM)); } return dependency; } @@ -246,9 +246,9 @@ public Dependency enforcedPlatform(Object notation) { if (platformDependency instanceof ExternalModuleDependency) { ExternalModuleDependency externalModuleDependency = (ExternalModuleDependency) platformDependency; externalModuleDependency.setForce(true); - PlatformSupport.addPlatformAttribute(externalModuleDependency, PlatformSupport.ENFORCED_PLATFORM); + PlatformSupport.addPlatformAttribute(externalModuleDependency, toCategory(Category.ENFORCED_PLATFORM)); } else if (platformDependency instanceof HasConfigurableAttributes) { - PlatformSupport.addPlatformAttribute((HasConfigurableAttributes) platformDependency, PlatformSupport.ENFORCED_PLATFORM); + PlatformSupport.addPlatformAttribute((HasConfigurableAttributes) platformDependency, toCategory(Category.ENFORCED_PLATFORM)); } return platformDependency; } @@ -260,6 +260,10 @@ public Dependency enforcedPlatform(Object notation, Action c return dep; } + private Category toCategory(String category) { + return namedObjectInstantiator.named(Category.class, category); + } + private class DirectDependencyAdder implements DynamicAddDependencyMethods.DependencyAdder { @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/PlatformSupport.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/PlatformSupport.java index ffec7c1050f2e..71931dd5963cd 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/PlatformSupport.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/dsl/dependencies/PlatformSupport.java @@ -18,68 +18,77 @@ import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; import org.gradle.api.Action; -import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.attributes.AttributeDisambiguationRule; import org.gradle.api.attributes.AttributeMatchingStrategy; import org.gradle.api.attributes.AttributesSchema; +import org.gradle.api.attributes.Category; import org.gradle.api.attributes.HasConfigurableAttributes; import org.gradle.api.attributes.MultipleCandidatesDetails; import org.gradle.api.internal.ReusableAction; +import org.gradle.api.internal.artifacts.repositories.metadata.MavenImmutableAttributesFactory; +import org.gradle.api.internal.model.NamedObjectInstantiator; import org.gradle.internal.component.external.model.ComponentVariant; import java.util.Set; public abstract class PlatformSupport { - public static final Attribute COMPONENT_CATEGORY = Attribute.of("org.gradle.component.category", String.class); - public static final String LIBRARY = "library"; - public static final String REGULAR_PLATFORM = "platform"; - public static final String ENFORCED_PLATFORM = "enforced-platform"; + + public static final Category REGULAR_PLATFORM = NamedObjectInstantiator.INSTANCE.named(Category.class, Category.REGULAR_PLATFORM); + public static final Category ENFORCED_PLATFORM = NamedObjectInstantiator.INSTANCE.named(Category.class, Category.ENFORCED_PLATFORM); public static boolean isTargettingPlatform(HasConfigurableAttributes target) { - String category = target.getAttributes().getAttribute(COMPONENT_CATEGORY); + Category category = target.getAttributes().getAttribute(Category.CATEGORY_ATTRIBUTE); return REGULAR_PLATFORM.equals(category) || ENFORCED_PLATFORM.equals(category); } public static void configureSchema(AttributesSchema attributesSchema) { - AttributeMatchingStrategy componentTypeMatchingStrategy = attributesSchema.attribute(PlatformSupport.COMPONENT_CATEGORY); + AttributeMatchingStrategy componentTypeMatchingStrategy = attributesSchema.attribute(Category.CATEGORY_ATTRIBUTE); componentTypeMatchingStrategy.getDisambiguationRules().add(PlatformSupport.ComponentCategoryDisambiguationRule.class); } - static void addPlatformAttribute(HasConfigurableAttributes dependency, final String type) { + static void addPlatformAttribute(HasConfigurableAttributes dependency, final Category category) { dependency.attributes(new Action() { @Override public void execute(AttributeContainer attributeContainer) { - attributeContainer.attribute(COMPONENT_CATEGORY, type); + attributeContainer.attribute(Category.CATEGORY_ATTRIBUTE, category); } }); } + /** + * Checks if the variant is an {@code enforced-platform} one. + *

+ * This method is designed to be called on parsed metadata and thus interacts with the {@code String} version of the attribute. + * + * @param variant the variant to test + * @return {@code true} if this represents an {@code enforced-platform}, {@code false} otherwise + */ public static boolean hasForcedDependencies(ComponentVariant variant) { - return Objects.equal(variant.getAttributes().getAttribute(COMPONENT_CATEGORY), ENFORCED_PLATFORM); + return Objects.equal(variant.getAttributes().getAttribute(MavenImmutableAttributesFactory.CATEGORY_ATTRIBUTE), Category.ENFORCED_PLATFORM); } - public static class ComponentCategoryDisambiguationRule implements AttributeDisambiguationRule, ReusableAction { + public static class ComponentCategoryDisambiguationRule implements AttributeDisambiguationRule, ReusableAction { @Override - public void execute(MultipleCandidatesDetails details) { - String consumerValue = details.getConsumerValue(); - Set candidateValues = details.getCandidateValues(); + public void execute(MultipleCandidatesDetails details) { + Category consumerValue = details.getConsumerValue(); + Set candidateValues = details.getCandidateValues(); if (consumerValue == null) { // consumer expressed no preference, defaults to library - if (candidateValues.contains(LIBRARY)) { - details.closestMatch(LIBRARY); - return; - } + candidateValues.stream() + .filter(it -> it.getName().equals(Category.LIBRARY)) + .findFirst() + .ifPresent(it -> details.closestMatch(it)); } } } - public static class PreferRegularPlatform implements AttributeDisambiguationRule { - private final static Set PLATFORM_TYPES = ImmutableSet.of(REGULAR_PLATFORM, ENFORCED_PLATFORM); + public static class PreferRegularPlatform implements AttributeDisambiguationRule { + private final static Set PLATFORM_TYPES = ImmutableSet.of(REGULAR_PLATFORM, ENFORCED_PLATFORM); @Override - public void execute(MultipleCandidatesDetails details) { + public void execute(MultipleCandidatesDetails details) { if (details.getCandidateValues().equals(PLATFORM_TYPES)) { details.closestMatch(REGULAR_PLATFORM); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ArtifactCacheLockingManager.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ArtifactCacheLockingManager.java index 470f4e60c8040..20863811d5e31 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ArtifactCacheLockingManager.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ArtifactCacheLockingManager.java @@ -15,7 +15,7 @@ */ package org.gradle.api.internal.artifacts.ivyservice; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.cache.CacheAccess; import org.gradle.cache.PersistentIndexedCache; import org.gradle.internal.serialize.Serializer; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyContextManager.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyContextManager.java index 660f384ff0323..904b7ac350052 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyContextManager.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/IvyContextManager.java @@ -16,7 +16,7 @@ package org.gradle.api.internal.artifacts.ivyservice; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.apache.ivy.Ivy; import org.gradle.api.Action; import org.gradle.api.Transformer; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java index 1ca8ca290894c..c7cc22743d1df 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutions.java @@ -104,6 +104,13 @@ public Action getRuleAction() { return Actions.composite(substitutionRules); } + private void addSubstitution(Action rule, boolean projectInvolved) { + addRule(rule); + if (projectInvolved) { + hasDependencySubstitutionRule = true; + } + } + private void addRule(Action rule) { mutationValidator.validateMutation(MutationValidator.MutationType.STRATEGY); substitutionRules.add(rule); @@ -146,6 +153,12 @@ public Substitution because(String description) { public void with(ComponentSelector substitute) { DefaultDependencySubstitution.validateTarget(substitute); + boolean projectInvolved = false; + if (substituted instanceof ProjectComponentSelector || substitute instanceof ProjectComponentSelector) { + // A project is involved, need to be aware of it + projectInvolved = true; + } + if (substituted instanceof UnversionedModuleComponentSelector) { final ModuleIdentifier moduleId = ((UnversionedModuleComponentSelector) substituted).getModuleIdentifier(); if (substitute instanceof ModuleComponentSelector) { @@ -154,9 +167,9 @@ public void with(ComponentSelector substitute) { substitutionReason = substitutionReason.markAsEquivalentToForce(); } } - all(new ModuleMatchDependencySubstitutionAction(substitutionReason, moduleId, substitute)); + addSubstitution(new ModuleMatchDependencySubstitutionAction(substitutionReason, moduleId, substitute), projectInvolved); } else { - all(new ExactMatchDependencySubstitutionAction(substitutionReason, substituted, substitute)); + addSubstitution(new ExactMatchDependencySubstitutionAction(substitutionReason, substituted, substitute), projectInvolved); } } }; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingModuleComponentRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingModuleComponentRepository.java index 93bc275adcd88..060d83571f636 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingModuleComponentRepository.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ErrorHandlingModuleComponentRepository.java @@ -145,11 +145,10 @@ public String toString() { public void listModuleVersions(ModuleDependencyMetadata dependency, BuildableModuleVersionListingResolveResult result) { performOperationWithRetries(result, () -> delegate.listModuleVersions(dependency, result), - () -> new ModuleVersionResolveException(dependency.getSelector(), BLACKLISTED_REPOSITORY_ERROR_MESSAGE), + () -> new ModuleVersionResolveException(dependency.getSelector(), () -> BLACKLISTED_REPOSITORY_ERROR_MESSAGE), throwable -> { ModuleComponentSelector selector = dependency.getSelector(); - String message = "Failed to list versions for " + selector.getGroup() + ":" + selector.getModule() + "."; - return new ModuleVersionResolveException(selector, message, throwable); + return new ModuleVersionResolveException(selector, () -> "Failed to list versions for " + selector.getGroup() + ":" + selector.getModule() + ".", throwable); }); } @@ -157,7 +156,7 @@ public void listModuleVersions(ModuleDependencyMetadata dependency, BuildableMod public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) { performOperationWithRetries(result, () -> delegate.resolveComponentMetaData(moduleComponentIdentifier, requestMetaData, result), - () -> new ModuleVersionResolveException(moduleComponentIdentifier, BLACKLISTED_REPOSITORY_ERROR_MESSAGE), + () -> new ModuleVersionResolveException(moduleComponentIdentifier, () -> BLACKLISTED_REPOSITORY_ERROR_MESSAGE), throwable -> new ModuleVersionResolveException(moduleComponentIdentifier, throwable) ); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoRepositoriesResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoRepositoriesResolver.java index 053783651f39b..bfd43681d9a16 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoRepositoriesResolver.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/NoRepositoriesResolver.java @@ -67,7 +67,7 @@ public OriginArtifactSelector getArtifactSelector() { @Override public void resolve(DependencyMetadata dependency, VersionSelector acceptor, VersionSelector rejector, BuildableComponentIdResolveResult result) { - result.failed(new ModuleVersionNotFoundException(dependency.getSelector(), String.format("Cannot resolve external dependency %s because no repositories are defined.", dependency.getSelector()))); + result.failed(new ModuleVersionNotFoundException(dependency.getSelector(), () -> String.format("Cannot resolve external dependency %s because no repositories are defined.", dependency.getSelector()))); } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainArtifactResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainArtifactResolver.java index 82fedb9d7b0bc..9e044fb8a93ad 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainArtifactResolver.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/RepositoryChainArtifactResolver.java @@ -15,9 +15,10 @@ */ package org.gradle.api.internal.artifacts.ivyservice.ivyresolve; -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport; +import org.gradle.api.attributes.Category; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ArtifactSet; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.excludes.ModuleExclusion; +import org.gradle.api.internal.artifacts.repositories.metadata.MavenImmutableAttributesFactory; import org.gradle.api.internal.artifacts.type.ArtifactTypeRegistry; import org.gradle.api.internal.attributes.AttributeValue; import org.gradle.api.internal.attributes.ImmutableAttributes; @@ -66,10 +67,10 @@ public ArtifactSet resolveArtifacts(ComponentResolveMetadata component, Configur } if (configuration.getArtifacts().isEmpty()) { // checks if it's a derived platform - AttributeValue componentTypeEntry = configuration.getAttributes().findEntry(PlatformSupport.COMPONENT_CATEGORY); + AttributeValue componentTypeEntry = configuration.getAttributes().findEntry(MavenImmutableAttributesFactory.CATEGORY_ATTRIBUTE); if (componentTypeEntry.isPresent()) { String value = componentTypeEntry.get(); - if (PlatformSupport.REGULAR_PLATFORM.equals(value) || PlatformSupport.ENFORCED_PLATFORM.equals(value)) { + if (Category.REGULAR_PLATFORM.equals(value) || Category.ENFORCED_PLATFORM.equals(value)) { return NO_ARTIFACTS; } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java index 0beab7d6b0970..6ea2b26643d5f 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/StartParameterResolutionOverride.java @@ -88,12 +88,12 @@ public String toString() { @Override public void listModuleVersions(ModuleDependencyMetadata dependency, BuildableModuleVersionListingResolveResult result) { - result.failed(new ModuleVersionResolveException(dependency.getSelector(), String.format("No cached version listing for %s available for offline mode.", dependency.getSelector()))); + result.failed(new ModuleVersionResolveException(dependency.getSelector(), () -> String.format("No cached version listing for %s available for offline mode.", dependency.getSelector()))); } @Override public void resolveComponentMetaData(ModuleComponentIdentifier moduleComponentIdentifier, ComponentOverrideMetadata requestMetaData, BuildableModuleComponentMetaDataResolveResult result) { - result.failed(new ModuleVersionResolveException(moduleComponentIdentifier, String.format("No cached version of %s available for offline mode.", moduleComponentIdentifier.getDisplayName()))); + result.failed(new ModuleVersionResolveException(moduleComponentIdentifier, () -> String.format("No cached version of %s available for offline mode.", moduleComponentIdentifier.getDisplayName()))); } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleMetadataParser.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradleModuleMetadataParser.java similarity index 91% rename from subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleMetadataParser.java rename to subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradleModuleMetadataParser.java index 0a68f03699960..3a294491b57de 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleMetadataParser.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradleModuleMetadataParser.java @@ -23,17 +23,20 @@ import org.gradle.api.Transformer; import org.gradle.api.artifacts.VersionConstraint; import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.Category; import org.gradle.api.capabilities.Capability; import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory; import org.gradle.api.internal.artifacts.ImmutableVersionConstraint; import org.gradle.api.internal.artifacts.dependencies.DefaultImmutableVersionConstraint; -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport; import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.DefaultExcludeRuleConverter; import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.ExcludeRuleConverter; +import org.gradle.api.internal.artifacts.repositories.metadata.MavenImmutableAttributesFactory; import org.gradle.api.internal.attributes.AttributeValue; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.api.internal.attributes.ImmutableAttributesFactory; import org.gradle.api.internal.model.NamedObjectInstantiator; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; import org.gradle.internal.component.external.model.MutableComponentVariant; import org.gradle.internal.component.external.model.MutableModuleComponentResolveMetadata; import org.gradle.internal.component.model.ExcludeMetadata; @@ -41,6 +44,7 @@ import org.gradle.internal.resource.local.LocallyAvailableExternalResource; import org.gradle.internal.snapshot.impl.CoercingStringValueSnapshot; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -51,15 +55,18 @@ import static com.google.gson.stream.JsonToken.BOOLEAN; import static com.google.gson.stream.JsonToken.END_ARRAY; import static com.google.gson.stream.JsonToken.END_OBJECT; +import static com.google.gson.stream.JsonToken.NUMBER; import static org.apache.commons.lang.StringUtils.capitalize; -public class ModuleMetadataParser { - public static final String FORMAT_VERSION = "0.4"; +public class GradleModuleMetadataParser { + private final static Logger LOGGER = Logging.getLogger(GradleModuleMetadataParser.class); + + public static final String FORMAT_VERSION = "1.0"; private final ImmutableAttributesFactory attributesFactory; private final NamedObjectInstantiator instantiator; private final ExcludeRuleConverter excludeRuleConverter; - public ModuleMetadataParser(ImmutableAttributesFactory attributesFactory, ImmutableModuleIdentifierFactory moduleIdentifierFactory, NamedObjectInstantiator instantiator) { + public GradleModuleMetadataParser(ImmutableAttributesFactory attributesFactory, ImmutableModuleIdentifierFactory moduleIdentifierFactory, NamedObjectInstantiator instantiator) { this.attributesFactory = attributesFactory; this.instantiator = instantiator; this.excludeRuleConverter = new DefaultExcludeRuleConverter(moduleIdentifierFactory); @@ -69,6 +76,7 @@ public void parse(final LocallyAvailableExternalResource resource, final Mutable resource.withContent(new Transformer() { @Override public Void transform(InputStream inputStream) { + String version = null; try { JsonReader reader = new JsonReader(new InputStreamReader(inputStream, "utf-8")); reader.beginObject(); @@ -82,14 +90,18 @@ public Void transform(InputStream inputStream) { if (reader.peek() != JsonToken.STRING) { throw new RuntimeException("The 'formatVersion' value should have a string value."); } - String version = reader.nextString(); - if (!version.equals(FORMAT_VERSION)) { - throw new RuntimeException(String.format("Unsupported format version '%s' specified in module metadata. This version of Gradle supports format version %s only.", version, FORMAT_VERSION)); - } + version = reader.nextString(); consumeTopLevelElements(reader, metadata); - metadata.setContentHash(HashUtil.createHash(resource.getFile(), "MD5")); + File file = resource.getFile(); + metadata.setContentHash(HashUtil.createHash(file, "MD5")); + if (!FORMAT_VERSION.equals(version)) { + LOGGER.debug("Unrecognized metadata format version '%s' found in '%s'. Parsing succeeded but it may lead to unexpected resolution results. Try upgrading to a newer version of Gradle", version, file); + } return null; } catch (Exception e) { + if (version != null && !FORMAT_VERSION.equals(version)) { + throw new MetaDataParseException("module metadata", resource, String.format("unsupported format version '%s' specified in module metadata. This version of Gradle supports format version %s.", version, FORMAT_VERSION), e); + } throw new MetaDataParseException("module metadata", resource, e); } } @@ -161,13 +173,12 @@ private void consumeVariant(JsonReader reader, MutableModuleComponentResolveMeta } } reader.endObject(); - MutableComponentVariant variant = metadata.addVariant(variantName, attributes); populateVariant(files, dependencies, dependencyConstraints, capabilities, variant); - AttributeValue entry = attributes.findEntry(PlatformSupport.COMPONENT_CATEGORY); - if (entry.isPresent() && PlatformSupport.REGULAR_PLATFORM.equals(entry.get())) { + AttributeValue entry = attributes.findEntry(MavenImmutableAttributesFactory.CATEGORY_ATTRIBUTE); + if (entry.isPresent() && Category.REGULAR_PLATFORM.equals(entry.get())) { // This generates a synthetic enforced platform variant with the same dependencies, similar to what the Maven variant derivation strategy does - ImmutableAttributes enforcedAttributes = attributesFactory.concat(attributes, attributesFactory.of(PlatformSupport.COMPONENT_CATEGORY, PlatformSupport.ENFORCED_PLATFORM)); + ImmutableAttributes enforcedAttributes = attributesFactory.concat(attributes, MavenImmutableAttributesFactory.CATEGORY_ATTRIBUTE, new CoercingStringValueSnapshot(Category.ENFORCED_PLATFORM, instantiator)); MutableComponentVariant syntheticEnforcedVariant = metadata.addVariant("enforced" + capitalize(variantName), enforcedAttributes); populateVariant(files, dependencies, dependencyConstraints, capabilities, syntheticEnforcedVariant); } @@ -390,6 +401,9 @@ private ImmutableAttributes consumeAttributes(JsonReader reader) throws IOExcept if (reader.peek() == BOOLEAN) { boolean attrValue = reader.nextBoolean(); attributes = attributesFactory.concat(attributes, Attribute.of(attrName, Boolean.class), attrValue); + } else if (reader.peek() == NUMBER) { + Integer attrValue = reader.nextInt(); + attributes = attributesFactory.concat(attributes, Attribute.of(attrName, Integer.class), attrValue); } else { String attrValue = reader.nextString(); attributes = attributesFactory.concat(attributes, Attribute.of(attrName, String.class), new CoercingStringValueSnapshot(attrValue, instantiator)); diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParseException.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParseException.java index f62276111cd47..791330e878f32 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParseException.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/MetaDataParseException.java @@ -29,4 +29,8 @@ public MetaDataParseException(String message) { public MetaDataParseException(String typeName, ExternalResource resource, Throwable cause) { super(String.format("Could not parse %s %s", typeName, resource.getDisplayName()), cause); } + + public MetaDataParseException(String typeName, ExternalResource resource, String details, Throwable cause) { + super(String.format("Could not parse %s %s: %s", typeName, resource.getDisplayName(), details), cause); + } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java index a3677f1cec003..bdb29c4ea06d1 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/projectmodule/ProjectDependencyResolver.java @@ -97,7 +97,7 @@ public void resolve(DependencyMetadata dependency, VersionSelector acceptor, Ver ProjectComponentIdentifier projectId = componentIdentifierFactory.createProjectComponentIdentifier(selector); LocalComponentMetadata componentMetaData = localComponentRegistry.getComponent(projectId); if (componentMetaData == null) { - result.failed(new ModuleVersionResolveException(selector, projectId + " not found.")); + result.failed(new ModuleVersionResolveException(selector, () -> projectId + " not found.")); } else { result.resolved(componentMetaData); } @@ -110,7 +110,7 @@ public void resolve(ComponentIdentifier identifier, ComponentOverrideMetadata co ProjectComponentIdentifier projectId = (ProjectComponentIdentifier) identifier; LocalComponentMetadata componentMetaData = localComponentRegistry.getComponent(projectId); if (componentMetaData == null) { - result.failed(new ModuleVersionResolveException(DefaultProjectComponentSelector.newSelector(projectId), projectId + " not found.")); + result.failed(new ModuleVersionResolveException(DefaultProjectComponentSelector.newSelector(projectId), () -> projectId + " not found.")); } else { result.resolved(componentMetaData); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasons.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasons.java index aef0dd92d4767..7193a3544b97d 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasons.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/ComponentSelectionReasons.java @@ -96,7 +96,7 @@ public boolean isSelectedByRule() { } public boolean isExpected() { - return isCauseExpected(Iterables.getLast(descriptions)); + return descriptions.size() == 1 && isCauseExpected(Iterables.getLast(descriptions)); } public boolean isCompositeSubstitution() { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilder.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilder.java index cf95a7c0b2e07..37c89f20d77a8 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilder.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultResolutionResultBuilder.java @@ -102,7 +102,7 @@ public void addExtraFailures(Long rootId, Set extraFailure ModuleComponentSelector failureComponentSelector = DefaultModuleComponentSelector.newSelector(failureSelector.getModule(), failureSelector.getVersion()); root.addDependency(dependencyResultFactory.createUnresolvedDependency(failureComponentSelector, root, true, ComponentSelectionReasons.of(new DefaultComponentSelectionDescriptor(ComponentSelectionCause.CONSTRAINT, Describables.of("Dependency locking"))), - new ModuleVersionResolveException(failureComponentSelector, "Dependency lock state out of date", failure.getProblem()))); + new ModuleVersionResolveException(failureComponentSelector, () -> "Dependency lock state out of date", failure.getProblem()))); } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DesugaredAttributeContainerSerializer.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DesugaredAttributeContainerSerializer.java index 9382132dfa9f4..8f738cbe033c6 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DesugaredAttributeContainerSerializer.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DesugaredAttributeContainerSerializer.java @@ -37,6 +37,7 @@ public class DesugaredAttributeContainerSerializer extends AbstractSerializer artifactFileStore; private final FileStore externalResourcesFileStore; private final MetaDataParser pomParser; - private final ModuleMetadataParser metadataParser; + private final GradleModuleMetadataParser metadataParser; private final AuthenticationSchemeRegistry authenticationSchemeRegistry; private final IvyContextManager ivyContextManager; private final ImmutableModuleIdentifierFactory moduleIdentifierFactory; @@ -84,7 +84,7 @@ public DefaultBaseRepositoryFactory(LocalMavenRepositoryLocator localMavenReposi FileStore artifactFileStore, FileStore externalResourcesFileStore, MetaDataParser pomParser, - ModuleMetadataParser metadataParser, + GradleModuleMetadataParser metadataParser, AuthenticationSchemeRegistry authenticationSchemeRegistry, IvyContextManager ivyContextManager, ImmutableModuleIdentifierFactory moduleIdentifierFactory, diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java index edeafbc67476d..61ab0cc301e2e 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepository.java @@ -39,7 +39,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyModuleDescriptorConverter; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser; -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser; +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser; import org.gradle.api.internal.artifacts.repositories.descriptor.IvyRepositoryDescriptor; import org.gradle.api.internal.artifacts.repositories.descriptor.RepositoryDescriptor; import org.gradle.api.internal.artifacts.repositories.layout.AbstractRepositoryLayout; @@ -98,7 +98,7 @@ public class DefaultIvyArtifactRepository extends AbstractAuthenticationSupporte private final ImmutableModuleIdentifierFactory moduleIdentifierFactory; private final InstantiatorFactory instantiatorFactory; private final FileResourceRepository fileResourceRepository; - private final ModuleMetadataParser moduleMetadataParser; + private final GradleModuleMetadataParser moduleMetadataParser; private final IvyMutableModuleMetadataFactory metadataFactory; private final IsolatableFactory isolatableFactory; private final IvyMetadataSources metadataSources = new IvyMetadataSources(); @@ -112,7 +112,7 @@ public DefaultIvyArtifactRepository(FileResolver fileResolver, RepositoryTranspo ImmutableModuleIdentifierFactory moduleIdentifierFactory, InstantiatorFactory instantiatorFactory, FileResourceRepository fileResourceRepository, - ModuleMetadataParser moduleMetadataParser, + GradleModuleMetadataParser moduleMetadataParser, FeaturePreviews featurePreviews, IvyMutableModuleMetadataFactory metadataFactory, IsolatableFactory isolatableFactory, diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java index 7d4bf27e01170..232f5e1feafb1 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepository.java @@ -33,7 +33,7 @@ import org.gradle.api.internal.artifacts.ModuleVersionPublisher; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ConfiguredModuleComponentRepository; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser; -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser; +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser; import org.gradle.api.internal.artifacts.repositories.descriptor.MavenRepositoryDescriptor; import org.gradle.api.internal.artifacts.repositories.descriptor.RepositoryDescriptor; import org.gradle.api.internal.artifacts.repositories.maven.MavenMetadataLoader; @@ -86,7 +86,7 @@ public boolean isUsableModule(String repoName, MutableMavenModuleResolveMetadata private final LocallyAvailableResourceFinder locallyAvailableResourceFinder; private final FileStore artifactFileStore; private final MetaDataParser pomParser; - private final ModuleMetadataParser metadataParser; + private final GradleModuleMetadataParser metadataParser; private final ImmutableModuleIdentifierFactory moduleIdentifierFactory; private final FileStore resourcesFileStore; private final FileResourceRepository fileResourceRepository; @@ -100,7 +100,7 @@ public DefaultMavenArtifactRepository(FileResolver fileResolver, RepositoryTrans InstantiatorFactory instantiatorFactory, FileStore artifactFileStore, MetaDataParser pomParser, - ModuleMetadataParser metadataParser, + GradleModuleMetadataParser metadataParser, AuthenticationContainer authenticationContainer, ImmutableModuleIdentifierFactory moduleIdentifierFactory, FileStore resourcesFileStore, @@ -120,7 +120,7 @@ public DefaultMavenArtifactRepository(Transformer artifactFileStore, MetaDataParser pomParser, - ModuleMetadataParser metadataParser, + GradleModuleMetadataParser metadataParser, AuthenticationContainer authenticationContainer, ImmutableModuleIdentifierFactory moduleIdentifierFactory, FileStore resourcesFileStore, @@ -273,7 +273,7 @@ private MetaDataParser getPomParser() { return pomParser; } - private ModuleMetadataParser getMetadataParser() { + private GradleModuleMetadataParser getMetadataParser() { return metadataParser; } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalArtifactRepository.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalArtifactRepository.java index b523cf1209d11..633cedaecd38c 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalArtifactRepository.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalArtifactRepository.java @@ -22,7 +22,7 @@ import org.gradle.internal.instantiation.InstantiatorFactory; import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser; -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser; +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser; import org.gradle.api.internal.artifacts.repositories.maven.MavenMetadataLoader; import org.gradle.api.internal.artifacts.repositories.metadata.DefaultMavenPomMetadataSource; import org.gradle.api.internal.artifacts.repositories.metadata.MavenMetadataArtifactProvider; @@ -56,7 +56,7 @@ public DefaultMavenLocalArtifactRepository(FileResolver fileResolver, Repository LocallyAvailableResourceFinder locallyAvailableResourceFinder, InstantiatorFactory instantiatorFactory, FileStore artifactFileStore, MetaDataParser pomParser, - ModuleMetadataParser metadataParser, + GradleModuleMetadataParser metadataParser, AuthenticationContainer authenticationContainer, ImmutableModuleIdentifierFactory moduleIdentifierFactory, FileResourceRepository fileResourceRepository, diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/DefaultGradleModuleMetadataSource.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/DefaultGradleModuleMetadataSource.java index 06bed095f3209..b20815bc9aac9 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/DefaultGradleModuleMetadataSource.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/DefaultGradleModuleMetadataSource.java @@ -18,7 +18,7 @@ import org.gradle.api.artifacts.ModuleIdentifier; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.ComponentResolvers; -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser; +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser; import org.gradle.api.internal.artifacts.repositories.resolver.ExternalResourceArtifactResolver; import org.gradle.api.internal.artifacts.repositories.resolver.ResourcePattern; import org.gradle.api.internal.artifacts.repositories.resolver.VersionLister; @@ -40,12 +40,12 @@ * Because of this, we will generate an empty instance (either a Ivy or Maven) based on the repository type. */ public class DefaultGradleModuleMetadataSource extends AbstractMetadataSource { - private final ModuleMetadataParser metadataParser; + private final GradleModuleMetadataParser metadataParser; private final MutableModuleMetadataFactory mutableModuleMetadataFactory; private final boolean listVersions; @Inject - public DefaultGradleModuleMetadataSource(ModuleMetadataParser metadataParser, MutableModuleMetadataFactory mutableModuleMetadataFactory, boolean listVersions) { + public DefaultGradleModuleMetadataSource(GradleModuleMetadataParser metadataParser, MutableModuleMetadataFactory mutableModuleMetadataFactory, boolean listVersions) { this.metadataParser = metadataParser; this.mutableModuleMetadataFactory = mutableModuleMetadataFactory; this.listVersions = listVersions; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/DefaultMavenImmutableAttributesFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/DefaultMavenImmutableAttributesFactory.java index b40c486bbc879..0a62818989a2f 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/DefaultMavenImmutableAttributesFactory.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/DefaultMavenImmutableAttributesFactory.java @@ -18,7 +18,7 @@ import com.google.common.base.Objects; import com.google.common.collect.Maps; import org.gradle.api.attributes.Attribute; -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport; +import org.gradle.api.attributes.Category; import org.gradle.api.internal.attributes.AttributeContainerInternal; import org.gradle.api.internal.attributes.AttributeMergingException; import org.gradle.api.internal.attributes.ImmutableAttributes; @@ -76,11 +76,11 @@ public ImmutableAttributes safeConcat(ImmutableAttributes attributes1, Immutable @Override public ImmutableAttributes libraryWithUsage(ImmutableAttributes original, String usage) { - ComponentTypeEntry entry = new ComponentTypeEntry(original, PlatformSupport.LIBRARY, usage); + ComponentTypeEntry entry = new ComponentTypeEntry(original, Category.LIBRARY, usage); ImmutableAttributes result = concatCache.get(entry); if (result == null) { result = concat(original, USAGE_ATTRIBUTE, new CoercingStringValueSnapshot(usage, objectInstantiator)); - result = concat(result, PlatformSupport.COMPONENT_CATEGORY, PlatformSupport.LIBRARY); + result = concat(result, CATEGORY_ATTRIBUTE, new CoercingStringValueSnapshot(Category.LIBRARY, objectInstantiator)); concatCache.put(entry, result); } return result; @@ -88,12 +88,12 @@ public ImmutableAttributes libraryWithUsage(ImmutableAttributes original, String @Override public ImmutableAttributes platformWithUsage(ImmutableAttributes original, String usage, boolean enforced) { - String componentType = enforced ? PlatformSupport.ENFORCED_PLATFORM : PlatformSupport.REGULAR_PLATFORM; + String componentType = enforced ? Category.ENFORCED_PLATFORM : Category.REGULAR_PLATFORM; ComponentTypeEntry entry = new ComponentTypeEntry(original, componentType, usage); ImmutableAttributes result = concatCache.get(entry); if (result == null) { result = concat(original, USAGE_ATTRIBUTE, new CoercingStringValueSnapshot(usage, objectInstantiator)); - result = concat(result, PlatformSupport.COMPONENT_CATEGORY, componentType); + result = concat(result, CATEGORY_ATTRIBUTE, new CoercingStringValueSnapshot(componentType, objectInstantiator)); concatCache.put(entry, result); } return result; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/MavenImmutableAttributesFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/MavenImmutableAttributesFactory.java index e4dc7408391f5..8eb3fc61d3016 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/MavenImmutableAttributesFactory.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/repositories/metadata/MavenImmutableAttributesFactory.java @@ -16,6 +16,7 @@ package org.gradle.api.internal.artifacts.repositories.metadata; import org.gradle.api.attributes.Attribute; +import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.api.internal.attributes.ImmutableAttributesFactory; @@ -29,6 +30,7 @@ public interface MavenImmutableAttributesFactory extends ImmutableAttributesFactory { // We need to work with the 'String' version of the usage attribute, since this is expected for all providers by the `PreferJavaRuntimeVariant` schema Attribute USAGE_ATTRIBUTE = Attribute.of(Usage.USAGE_ATTRIBUTE.getName(), String.class); + Attribute CATEGORY_ATTRIBUTE = Attribute.of(Category.CATEGORY_ATTRIBUTE.getName(), String.class); ImmutableAttributes libraryWithUsage(ImmutableAttributes original, String usage); ImmutableAttributes platformWithUsage(ImmutableAttributes original, String usage, boolean enforced); diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ArtifactTransformActionScheme.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ArtifactTransformActionScheme.java index a9fe29d23f801..539ad28e70d50 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ArtifactTransformActionScheme.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ArtifactTransformActionScheme.java @@ -16,10 +16,13 @@ package org.gradle.api.internal.artifacts.transform; +import org.gradle.api.artifacts.transform.TransformAction; import org.gradle.api.internal.tasks.properties.InspectionScheme; +import org.gradle.api.internal.tasks.properties.TypeMetadataStore; +import org.gradle.api.internal.tasks.properties.TypeScheme; import org.gradle.internal.instantiation.InstantiationScheme; -public class ArtifactTransformActionScheme { +public class ArtifactTransformActionScheme implements TypeScheme { private final InstantiationScheme instantiationScheme; private final InspectionScheme inspectionScheme; private final InstantiationScheme legacyInstantiationScheme; @@ -30,6 +33,16 @@ public ArtifactTransformActionScheme(InstantiationScheme instantiationScheme, In this.legacyInstantiationScheme = legacyInstantiationScheme; } + @Override + public TypeMetadataStore getMetadataStore() { + return inspectionScheme.getMetadataStore(); + } + + @Override + public boolean appliesTo(Class type) { + return TransformAction.class.isAssignableFrom(type); + } + public InspectionScheme getInspectionScheme() { return inspectionScheme; } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ArtifactTransformParameterScheme.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ArtifactTransformParameterScheme.java index feed182b9daaa..4498dca717160 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ArtifactTransformParameterScheme.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ArtifactTransformParameterScheme.java @@ -16,10 +16,13 @@ package org.gradle.api.internal.artifacts.transform; +import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.api.internal.tasks.properties.InspectionScheme; +import org.gradle.api.internal.tasks.properties.TypeMetadataStore; +import org.gradle.api.internal.tasks.properties.TypeScheme; import org.gradle.internal.instantiation.InstantiationScheme; -public class ArtifactTransformParameterScheme { +public class ArtifactTransformParameterScheme implements TypeScheme { private final InstantiationScheme instantiationScheme; private final InspectionScheme inspectionScheme; @@ -28,6 +31,16 @@ public ArtifactTransformParameterScheme(InstantiationScheme instantiationScheme, this.inspectionScheme = inspectionScheme; } + @Override + public TypeMetadataStore getMetadataStore() { + return inspectionScheme.getMetadataStore(); + } + + @Override + public boolean appliesTo(Class type) { + return TransformParameters.class.isAssignableFrom(type); + } + public InstantiationScheme getInstantiationScheme() { return instantiationScheme; } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/AttributeMatchingVariantSelector.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/AttributeMatchingVariantSelector.java index 0868bbd736920..1f3bde6fc471d 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/AttributeMatchingVariantSelector.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/AttributeMatchingVariantSelector.java @@ -36,11 +36,13 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; class AttributeMatchingVariantSelector implements VariantSelector { private final ConsumerProvidedVariantFinder consumerProvidedVariantFinder; private final AttributesSchemaInternal schema; private final ImmutableAttributesFactory attributesFactory; + private final TransformationNodeRegistry transformationNodeRegistry; private final ImmutableAttributes requested; private final boolean ignoreWhenNoMatches; private final ExtraExecutionGraphDependenciesResolverFactory dependenciesResolver; @@ -49,6 +51,7 @@ class AttributeMatchingVariantSelector implements VariantSelector { ConsumerProvidedVariantFinder consumerProvidedVariantFinder, AttributesSchemaInternal schema, ImmutableAttributesFactory attributesFactory, + TransformationNodeRegistry transformationNodeRegistry, AttributeContainerInternal requested, boolean ignoreWhenNoMatches, ExtraExecutionGraphDependenciesResolverFactory dependenciesResolver @@ -56,6 +59,7 @@ class AttributeMatchingVariantSelector implements VariantSelector { this.consumerProvidedVariantFinder = consumerProvidedVariantFinder; this.schema = schema; this.attributesFactory = attributesFactory; + this.transformationNodeRegistry = transformationNodeRegistry; this.requested = requested.asImmutable(); this.ignoreWhenNoMatches = ignoreWhenNoMatches; this.dependenciesResolver = dependenciesResolver; @@ -98,14 +102,14 @@ private ResolvedArtifactSet doSelect(ResolvedVariantSet producer) { } } if (candidates.size() > 1) { - candidates = tryDisambiguate(matcher, candidates); + candidates = tryDisambiguate(matcher, candidates, componentRequested); } if (candidates.size() == 1) { Pair result = candidates.get(0); ResolvedArtifactSet artifacts = result.getLeft().getArtifacts(); AttributeContainerInternal attributes = result.getRight().attributes; Transformation transformation = result.getRight().transformation; - return new ConsumerProvidedResolvedVariant(producer.getComponentId(), artifacts, attributes, transformation, dependenciesResolver); + return new ConsumerProvidedResolvedVariant(producer.getComponentId(), artifacts, attributes, transformation, dependenciesResolver, transformationNodeRegistry); } if (!candidates.isEmpty()) { @@ -118,7 +122,13 @@ private ResolvedArtifactSet doSelect(ResolvedVariantSet producer) { throw new NoMatchingVariantSelectionException(producer.asDescribable().getDisplayName(), componentRequested, producer.getVariants(), matcher); } - private List> tryDisambiguate(AttributeMatcher matcher, List> candidates) { + private List> tryDisambiguate(AttributeMatcher matcher, List> candidates, ImmutableAttributes componentRequested) { + candidates = disambiguateWithSchema(matcher, candidates, componentRequested); + + if (candidates.size() == 1) { + return candidates; + } + if (candidates.size() == 2) { // Short circuit logic when only 2 candidates return compareCandidates(matcher, candidates.get(0), candidates.get(1)) @@ -161,6 +171,19 @@ private List> return shortestTransforms; } + private List> disambiguateWithSchema(AttributeMatcher matcher, List> candidates, ImmutableAttributes componentRequested) { + List candidateAttributes = candidates.stream().map(pair -> pair.getRight().attributes).collect(Collectors.toList()); + List matches = matcher.matches(candidateAttributes, componentRequested); + if (matches.size() == 1) { + AttributeContainerInternal singleMatch = matches.get(0); + return candidates.stream().filter(pair -> pair.getRight().attributes.equals(singleMatch)).collect(Collectors.toList()); + } else if (matches.size() > 0 && matches.size() < candidates.size()) { + // We know all are compatibles, so this is only possible if some disambiguation happens but not getting us to 1 candidate + return candidates.stream().filter(pair -> matches.contains(pair.getRight().attributes)).collect(Collectors.toList()); + } + return candidates; + } + private Optional> compareCandidates(AttributeMatcher matcher, Pair firstCandidate, Pair secondCandidate) { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/CacheableTransformTypeAnnotationHandler.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/CacheableTransformTypeAnnotationHandler.java new file mode 100644 index 0000000000000..97dcb51b6ef77 --- /dev/null +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/CacheableTransformTypeAnnotationHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.artifacts.transform; + +import org.gradle.api.artifacts.transform.CacheableTransform; +import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.internal.tasks.properties.annotations.TypeAnnotationHandler; +import org.gradle.internal.reflect.ParameterValidationContext; +import org.gradle.model.internal.type.ModelType; + +import java.lang.annotation.Annotation; + +public class CacheableTransformTypeAnnotationHandler implements TypeAnnotationHandler { + @Override + public Class getAnnotationType() { + return CacheableTransform.class; + } + + @Override + public void validateTypeMetadata(Class classWithAnnotationAttached, ParameterValidationContext visitor) { + if (!TransformAction.class.isAssignableFrom(classWithAnnotationAttached)) { + visitor.visitErrorStrict(String.format("Cannot use @%s with type %s. This annotation can only be used with %s types.", getAnnotationType().getSimpleName(), ModelType.of(classWithAnnotationAttached).getDisplayName(), TransformAction.class.getSimpleName())); + } + } +} diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ConsumerProvidedResolvedVariant.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ConsumerProvidedResolvedVariant.java index ba49dde0033ae..2ef5f6c22a750 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ConsumerProvidedResolvedVariant.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/ConsumerProvidedResolvedVariant.java @@ -37,20 +37,29 @@ public class ConsumerProvidedResolvedVariant implements ResolvedArtifactSet { private final AttributeContainerInternal attributes; private final Transformation transformation; private final ExtraExecutionGraphDependenciesResolverFactory resolverFactory; + private final TransformationNodeRegistry transformationNodeRegistry; - public ConsumerProvidedResolvedVariant(ComponentIdentifier componentIdentifier, ResolvedArtifactSet delegate, AttributeContainerInternal target, Transformation transformation, ExtraExecutionGraphDependenciesResolverFactory dependenciesResolverFactory) { + public ConsumerProvidedResolvedVariant( + ComponentIdentifier componentIdentifier, + ResolvedArtifactSet delegate, + AttributeContainerInternal target, + Transformation transformation, + ExtraExecutionGraphDependenciesResolverFactory dependenciesResolverFactory, + TransformationNodeRegistry transformationNodeRegistry + ) { this.componentIdentifier = componentIdentifier; this.delegate = delegate; this.attributes = target; this.transformation = transformation; this.resolverFactory = dependenciesResolverFactory; + this.transformationNodeRegistry = transformationNodeRegistry; } @Override public Completion startVisit(BuildOperationQueue actions, AsyncArtifactListener listener) { - Map artifactResults = Maps.newConcurrentMap(); - Map fileResults = Maps.newConcurrentMap(); - Completion result = delegate.startVisit(actions, new TransformingAsyncArtifactListener(transformation, listener, actions, artifactResults, fileResults, getDependenciesResolver())); + Map artifactResults = Maps.newConcurrentMap(); + Map fileResults = Maps.newConcurrentMap(); + Completion result = delegate.startVisit(actions, new TransformingAsyncArtifactListener(transformation, listener, actions, artifactResults, fileResults, getDependenciesResolver(), transformationNodeRegistry)); return new TransformCompletion(result, attributes, artifactResults, fileResults); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultArtifactTransforms.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultArtifactTransforms.java index 3173a6d173d02..00a1ba658778e 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultArtifactTransforms.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultArtifactTransforms.java @@ -24,20 +24,23 @@ public class DefaultArtifactTransforms implements ArtifactTransforms { private final ConsumerProvidedVariantFinder consumerProvidedVariantFinder; private final AttributesSchemaInternal schema; private final ImmutableAttributesFactory attributesFactory; + private final TransformationNodeRegistry transformationNodeRegistry; public DefaultArtifactTransforms( ConsumerProvidedVariantFinder consumerProvidedVariantFinder, AttributesSchemaInternal schema, - ImmutableAttributesFactory attributesFactory + ImmutableAttributesFactory attributesFactory, + TransformationNodeRegistry transformationNodeRegistry ) { this.consumerProvidedVariantFinder = consumerProvidedVariantFinder; this.schema = schema; this.attributesFactory = attributesFactory; + this.transformationNodeRegistry = transformationNodeRegistry; } @Override public VariantSelector variantSelector(AttributeContainerInternal consumerAttributes, boolean allowNoMatchingVariants, ExtraExecutionGraphDependenciesResolverFactory dependenciesResolver) { - return new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, schema, attributesFactory, consumerAttributes.asImmutable(), allowNoMatchingVariants, dependenciesResolver); + return new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, schema, attributesFactory, transformationNodeRegistry, consumerAttributes.asImmutable(), allowNoMatchingVariants, dependenciesResolver); } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultExecutionGraphDependenciesResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultExecutionGraphDependenciesResolver.java index f615e6354423e..bdbc5bfb592eb 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultExecutionGraphDependenciesResolver.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultExecutionGraphDependenciesResolver.java @@ -45,7 +45,7 @@ public class DefaultExecutionGraphDependenciesResolver implements ExecutionGraph private static final ArtifactTransformDependencies MISSING_DEPENDENCIES = new ArtifactTransformDependencies() { @Override public FileCollection getFiles() { - throw new IllegalStateException("Tranform does not use artifact dependencies."); + throw new IllegalStateException("Transform does not use artifact dependencies."); } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationNodeFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationNodeRegistry.java similarity index 88% rename from subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationNodeFactory.java rename to subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationNodeRegistry.java index 02399d5cad558..e8aac06dfb290 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationNodeFactory.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationNodeRegistry.java @@ -27,9 +27,10 @@ import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.function.Function; -public class DefaultTransformationNodeFactory implements TransformationNodeFactory { +public class DefaultTransformationNodeRegistry implements TransformationNodeRegistry { private final Map transformations = Maps.newConcurrentMap(); @Override @@ -43,6 +44,17 @@ public Collection getOrCreate(ResolvedArtifactSet artifactSe return builder.build(); } + @Override + public Optional getCompleted(ComponentArtifactIdentifier artifactId, Transformation transformation) { + List> transformationChain = unpackTransformation(transformation); + ArtifactTransformKey transformKey = new ArtifactTransformKey(artifactId, transformationChain); + TransformationNode node = transformations.get(transformKey); + if (node != null && node.isComplete()) { + return Optional.of(node); + } + return Optional.empty(); + } + private void collectTransformNodes(ResolvedArtifactSet artifactSet, ImmutableList.Builder builder, Function nodeCreator) { artifactSet.visitLocalArtifacts(new ResolvedArtifactSet.LocalArtifactVisitor() { @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationRegistrationFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationRegistrationFactory.java index 209d9a2891cba..ef26de0272956 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationRegistrationFactory.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformationRegistrationFactory.java @@ -18,10 +18,11 @@ import org.gradle.api.InvalidUserDataException; import org.gradle.api.artifacts.transform.ArtifactTransform; -import org.gradle.api.artifacts.transform.CacheableTransformAction; +import org.gradle.api.artifacts.transform.CacheableTransform; import org.gradle.api.artifacts.transform.InputArtifact; import org.gradle.api.artifacts.transform.InputArtifactDependencies; import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.api.internal.artifacts.ArtifactTransformRegistration; import org.gradle.api.internal.attributes.AttributeContainerInternal; import org.gradle.api.internal.attributes.ImmutableAttributes; @@ -88,12 +89,12 @@ public DefaultTransformationRegistrationFactory( } @Override - public ArtifactTransformRegistration create(ImmutableAttributes from, ImmutableAttributes to, Class implementation, @Nullable Object parameterObject) { + public ArtifactTransformRegistration create(ImmutableAttributes from, ImmutableAttributes to, Class implementation, @Nullable TransformParameters parameterObject) { List validationMessages = new ArrayList<>(); TypeMetadata actionMetadata = actionMetadataStore.getTypeMetadata(implementation); DefaultParameterValidationContext parameterValidationContext = new DefaultParameterValidationContext(validationMessages); actionMetadata.collectValidationFailures(null, parameterValidationContext); - boolean cacheable = implementation.isAnnotationPresent(CacheableTransformAction.class); + boolean cacheable = implementation.isAnnotationPresent(CacheableTransform.class); // Should retain this on the metadata rather than calculate on each invocation Class inputArtifactNormalizer = null; @@ -129,18 +130,16 @@ public ArtifactTransformRegistration create(ImmutableAttributes from, ImmutableA isolatableFactory, valueSnapshotter, fileCollectionFactory, - fileCollectionFingerprinterRegistry, parametersPropertyWalker, - domainObjectProjectStateHandler, actionInstantiationScheme); - return new DefaultArtifactTransformRegistration(from, to, new TransformationStep(transformer, transformerInvoker)); + return new DefaultArtifactTransformRegistration(from, to, new TransformationStep(transformer, transformerInvoker, domainObjectProjectStateHandler, fileCollectionFingerprinterRegistry)); } @Override public ArtifactTransformRegistration create(ImmutableAttributes from, ImmutableAttributes to, Class implementation, Object[] params) { Transformer transformer = new LegacyTransformer(implementation, params, legacyActionInstantiationScheme, from, classLoaderHierarchyHasher, isolatableFactory); - return new DefaultArtifactTransformRegistration(from, to, new TransformationStep(transformer, transformerInvoker)); + return new DefaultArtifactTransformRegistration(from, to, new TransformationStep(transformer, transformerInvoker, domainObjectProjectStateHandler, fileCollectionFingerprinterRegistry)); } private static class DefaultArtifactTransformRegistration implements ArtifactTransformRegistration { @@ -174,7 +173,7 @@ private static class NormalizerCollectingVisitor extends PropertyVisitor.Adapter private Class normalizer; @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { this.normalizer = fileNormalizer; } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformer.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformer.java index 6dfc968458f75..4db3862926c98 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformer.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformer.java @@ -20,18 +20,17 @@ import com.google.common.collect.ImmutableSortedMap; import com.google.common.reflect.TypeToken; import org.gradle.api.InvalidUserDataException; -import org.gradle.api.Project; import org.gradle.api.artifacts.transform.InputArtifact; import org.gradle.api.artifacts.transform.InputArtifactDependencies; import org.gradle.api.artifacts.transform.TransformAction; import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.api.artifacts.transform.VariantTransformConfigurationException; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileSystemLocation; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.api.internal.file.FileCollectionFactory; -import org.gradle.api.internal.project.ProjectStateRegistry; +import org.gradle.api.internal.plugins.DslObject; import org.gradle.api.internal.tasks.TaskDependencyResolveContext; -import org.gradle.api.internal.tasks.WorkNodeAction; import org.gradle.api.internal.tasks.properties.DefaultParameterValidationContext; import org.gradle.api.internal.tasks.properties.FileParameterUtils; import org.gradle.api.internal.tasks.properties.InputFilePropertyType; @@ -40,6 +39,7 @@ import org.gradle.api.internal.tasks.properties.PropertyValue; import org.gradle.api.internal.tasks.properties.PropertyVisitor; import org.gradle.api.internal.tasks.properties.PropertyWalker; +import org.gradle.api.provider.Provider; import org.gradle.api.reflect.InjectionPointQualifier; import org.gradle.api.tasks.FileNormalizer; import org.gradle.internal.classloader.ClassLoaderHierarchyHasher; @@ -53,7 +53,6 @@ import org.gradle.internal.hash.Hashing; import org.gradle.internal.instantiation.InstanceFactory; import org.gradle.internal.instantiation.InstantiationScheme; -import org.gradle.internal.instantiation.Managed; import org.gradle.internal.isolation.Isolatable; import org.gradle.internal.isolation.IsolatableFactory; import org.gradle.internal.reflect.ParameterValidationContext; @@ -63,6 +62,7 @@ import org.gradle.internal.snapshot.ValueSnapshot; import org.gradle.internal.snapshot.ValueSnapshotter; import org.gradle.model.internal.type.ModelType; +import org.gradle.work.InputChanges; import javax.annotation.Nullable; import java.io.File; @@ -71,31 +71,29 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.function.Supplier; import java.util.stream.Collectors; public class DefaultTransformer extends AbstractTransformer { - private final Object parameterObject; + private final TransformParameters parameterObject; private final Class fileNormalizer; private final Class dependenciesNormalizer; private final ClassLoaderHierarchyHasher classLoaderHierarchyHasher; private final IsolatableFactory isolatableFactory; private final ValueSnapshotter valueSnapshotter; private final FileCollectionFactory fileCollectionFactory; - private final FileCollectionFingerprinterRegistry fileCollectionFingerprinterRegistry; private final PropertyWalker parameterPropertyWalker; private final boolean requiresDependencies; + private final boolean requiresInputChanges; private final InstanceFactory instanceFactory; - private final DomainObjectProjectStateHandler projectStateHandler; - private final ProjectStateRegistry.SafeExclusiveLock isolationLock; - private final WorkNodeAction isolateAction; private final boolean cacheable; - private IsolatableParameters isolatable; + private IsolatedParameters isolatedParameters; public DefaultTransformer( Class implementationClass, - @Nullable Object parameterObject, + @Nullable TransformParameters parameterObject, ImmutableAttributes fromAttributes, Class inputArtifactNormalizer, Class dependenciesNormalizer, @@ -104,9 +102,7 @@ public DefaultTransformer( IsolatableFactory isolatableFactory, ValueSnapshotter valueSnapshotter, FileCollectionFactory fileCollectionFactory, - FileCollectionFingerprinterRegistry fileCollectionFingerprinterRegistry, PropertyWalker parameterPropertyWalker, - DomainObjectProjectStateHandler projectStateHandler, InstantiationScheme actionInstantiationScheme ) { super(implementationClass, fromAttributes); @@ -117,34 +113,20 @@ public DefaultTransformer( this.isolatableFactory = isolatableFactory; this.valueSnapshotter = valueSnapshotter; this.fileCollectionFactory = fileCollectionFactory; - this.fileCollectionFingerprinterRegistry = fileCollectionFingerprinterRegistry; this.parameterPropertyWalker = parameterPropertyWalker; this.instanceFactory = actionInstantiationScheme.forType(implementationClass); this.requiresDependencies = instanceFactory.serviceInjectionTriggeredByAnnotation(InputArtifactDependencies.class); + this.requiresInputChanges = instanceFactory.requiresService(InputChanges.class); this.cacheable = cacheable; - this.projectStateHandler = projectStateHandler; - this.isolationLock = projectStateHandler.newExclusiveOperationLock(); - this.isolateAction = parameterObject == null ? null : new WorkNodeAction() { - @Nullable - @Override - public Project getProject() { - return projectStateHandler.maybeGetOwningProject(); - } - - @Override - public void run() { - isolateExclusively(); - } - }; } public static void validateInputFileNormalizer(String propertyName, @Nullable Class normalizer, boolean cacheable, ParameterValidationContext parameterValidationContext) { if (cacheable) { if (normalizer == AbsolutePathInputNormalizer.class) { - parameterValidationContext.recordValidationMessage(null, propertyName, "is declared to be sensitive to absolute paths. This is not allowed for cacheable transforms"); + parameterValidationContext.visitError(null, propertyName, "is declared to be sensitive to absolute paths. This is not allowed for cacheable transforms"); } if (normalizer == null) { - parameterValidationContext.recordValidationMessage(null, propertyName, "is declared without path sensitivity. Properties of cacheable transforms must declare their path sensitivity"); + parameterValidationContext.visitError(null, propertyName, "is declared without path sensitivity. Properties of cacheable transforms must declare their path sensitivity"); } } } @@ -159,10 +141,20 @@ public Class getInputArtifactDependenciesNormalizer() return dependenciesNormalizer; } + @Override + public boolean isIsolated() { + return isolatedParameters != null; + } + public boolean requiresDependencies() { return requiresDependencies; } + @Override + public boolean requiresInputChanges() { + return requiresInputChanges; + } + @Override public boolean isCacheable() { return cacheable; @@ -170,68 +162,53 @@ public boolean isCacheable() { @Override public HashCode getSecondaryInputHash() { - return getIsolatable().getSecondaryInputsHash(); + return getIsolatedParameters().getSecondaryInputsHash(); } @Override - public ImmutableList transform(File inputArtifact, File outputDir, ArtifactTransformDependencies dependencies) { - TransformAction transformAction = newTransformAction(inputArtifact, dependencies); - DefaultTransformOutputs transformOutputs = new DefaultTransformOutputs(inputArtifact, outputDir); + public ImmutableList transform(Provider inputArtifactProvider, File outputDir, ArtifactTransformDependencies dependencies, @Nullable InputChanges inputChanges) { + TransformAction transformAction = newTransformAction(inputArtifactProvider, dependencies, inputChanges); + DefaultTransformOutputs transformOutputs = new DefaultTransformOutputs(inputArtifactProvider.get().getAsFile(), outputDir); transformAction.transform(transformOutputs); return transformOutputs.getRegisteredOutputs(); } @Override public void visitDependencies(TaskDependencyResolveContext context) { - if (isolateAction != null) { - context.add(isolateAction); - } if (parameterObject != null) { parameterPropertyWalker.visitProperties(parameterObject, ParameterValidationContext.NOOP, new PropertyVisitor.Adapter() { @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { - context.maybeAdd(value.call()); + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + Object unpacked = value.call(); + if (unpacked != null) { + context.maybeAdd(unpacked); + } } }); } } @Override - public void isolateParameters() { - if (isolatable == null) { - if (!projectStateHandler.hasMutableProjectState()) { - projectStateHandler.withLenientState(this::isolateExclusively); - } else { - isolateExclusively(); - } + public void isolateParameters(FileCollectionFingerprinterRegistry fingerprinterRegistry) { + try { + isolatedParameters = doIsolateParameters(fingerprinterRegistry); + } catch (Exception e) { + throw new VariantTransformConfigurationException(String.format("Cannot isolate parameters %s of artifact transform %s", parameterObject, ModelType.of(getImplementationClass()).getDisplayName()), e); } } - private void isolateExclusively() { - isolationLock.withLock(() -> { - if (isolatable != null) { - return; - } - try { - isolatable = doIsolateParameters(); - } catch (Exception e) { - throw new VariantTransformConfigurationException(String.format("Cannot isolate parameters %s of artifact transform %s", parameterObject, ModelType.of(getImplementationClass()).getDisplayName()), e); - } - }); - } - - protected IsolatableParameters doIsolateParameters() { - Isolatable isolatableParameterObject = isolatableFactory.isolate(parameterObject); + protected IsolatedParameters doIsolateParameters(FileCollectionFingerprinterRegistry fingerprinterRegistry) { + Isolatable isolatedParameterObject = isolatableFactory.isolate(parameterObject); Hasher hasher = Hashing.newHasher(); appendActionImplementation(getImplementationClass(), hasher, classLoaderHierarchyHasher); if (parameterObject != null) { // TODO wolfs - schedule fingerprinting separately, it can be done without having the project lock - fingerprintParameters(valueSnapshotter, fileCollectionFingerprinterRegistry, fileCollectionFactory, parameterPropertyWalker, hasher, isolatableParameterObject.isolate(), cacheable); + fingerprintParameters(valueSnapshotter, fingerprinterRegistry, fileCollectionFactory, parameterPropertyWalker, hasher, isolatedParameterObject.isolate(), cacheable); } HashCode secondaryInputsHash = hasher.hash(); - return new IsolatableParameters(isolatableParameterObject, secondaryInputsHash); + return new IsolatedParameters(isolatedParameterObject, secondaryInputsHash); } private static void fingerprintParameters( @@ -254,7 +231,7 @@ public void visitInputProperty(String propertyName, PropertyValue value, boolean Object preparedValue = InputParameterUtils.prepareInputParameterValue(value); if (preparedValue == null && !optional) { - validationContext.recordValidationMessage(null, propertyName, "does not have a value specified"); + validationContext.visitError(null, propertyName, "does not have a value specified"); } inputParameterFingerprintsBuilder.put(propertyName, valueSnapshotter.snapshot(preparedValue)); @@ -269,11 +246,11 @@ public void visitInputProperty(String propertyName, PropertyValue value, boolean @Override public void visitOutputFileProperty(String propertyName, boolean optional, PropertyValue value, OutputFilePropertyType filePropertyType) { - validationContext.recordValidationMessage(null, propertyName, "is annotated with an output annotation"); + validationContext.visitError(null, propertyName, "is annotated with an output annotation"); } @Override - public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { + public void visitInputFileProperty(String propertyName, boolean optional, boolean skipWhenEmpty, boolean incremental, @Nullable Class fileNormalizer, PropertyValue value, InputFilePropertyType filePropertyType) { validateInputFileNormalizer(propertyName, fileNormalizer, cacheable, validationContext); FileCollectionFingerprinter fingerprinter = fingerprinterRegistry.getFingerprinter(FileParameterUtils.normalizerOrDefault(fileNormalizer)); FileCollection inputFileValue = FileParameterUtils.resolveInputFileValue(fileCollectionFactory, filePropertyType, value); @@ -300,33 +277,42 @@ public void visitInputFileProperty(String propertyName, boolean optional, boolea } private static String getParameterObjectDisplayName(Object parameterObject) { - Class parameterClass = parameterObject instanceof Managed ? ((Managed) parameterObject).publicType() : parameterObject.getClass(); - return ModelType.of(parameterClass).getDisplayName(); + return ModelType.of(new DslObject(parameterObject).getDeclaredType()).getDisplayName(); } - private TransformAction newTransformAction(File inputFile, ArtifactTransformDependencies artifactTransformDependencies) { - ServiceLookup services = new TransformServiceLookup(inputFile, getIsolatable().getIsolatableParameters().isolate(), requiresDependencies ? artifactTransformDependencies : null); + private TransformAction newTransformAction(Provider inputArtifactProvider, ArtifactTransformDependencies artifactTransformDependencies, @Nullable InputChanges inputChanges) { + ServiceLookup services = new TransformServiceLookup(inputArtifactProvider, getIsolatedParameters().getIsolatedParameterObject().isolate(), requiresDependencies ? artifactTransformDependencies : null, inputChanges); return instanceFactory.newInstance(services); } - private IsolatableParameters getIsolatable() { - if (isolatable == null) { + private IsolatedParameters getIsolatedParameters() { + if (isolatedParameters == null) { throw new IllegalStateException("The parameters of " + getDisplayName() + "need to be isolated first!"); } - return isolatable; + return isolatedParameters; } private static class TransformServiceLookup implements ServiceLookup { + private static final Type FILE_SYSTEM_LOCATION_PROVIDER = new TypeToken>() {}.getType(); + private final ImmutableList injectionPoints; - public TransformServiceLookup(File inputFile, @Nullable Object parameters, @Nullable ArtifactTransformDependencies artifactTransformDependencies) { + public TransformServiceLookup(Provider inputFileProvider, @Nullable TransformParameters parameters, @Nullable ArtifactTransformDependencies artifactTransformDependencies, @Nullable InputChanges inputChanges) { ImmutableList.Builder builder = ImmutableList.builder(); - builder.add(new InjectionPoint(InputArtifact.class, File.class, inputFile)); + builder.add(InjectionPoint.injectedByAnnotation(InputArtifact.class, File.class, () -> inputFileProvider.get().getAsFile())); + builder.add(InjectionPoint.injectedByAnnotation(InputArtifact.class, FILE_SYSTEM_LOCATION_PROVIDER, () -> inputFileProvider)); if (parameters != null) { - builder.add(new InjectionPoint(TransformParameters.class, parameters.getClass(), parameters)); + builder.add(InjectionPoint.injectedByType(parameters.getClass(), () -> parameters)); + } else { + builder.add(InjectionPoint.injectedByType(TransformParameters.None.class, () -> { + throw new UnknownServiceException(TransformParameters.None.class, "Cannot query parameters for artifact transform without parameters."); + })); } if (artifactTransformDependencies != null) { - builder.add(new InjectionPoint(InputArtifactDependencies.class, artifactTransformDependencies.getFiles())); + builder.add(InjectionPoint.injectedByAnnotation(InputArtifactDependencies.class, () -> artifactTransformDependencies.getFiles())); + } + if (inputChanges != null) { + builder.add(InjectionPoint.injectedByType(InputChanges.class, () -> inputChanges)); } this.injectionPoints = builder.build(); } @@ -368,19 +354,27 @@ public Object get(Type serviceType, Class annotatedWith) t private static class InjectionPoint { private final Class annotation; - private final Class injectedType; - private final Object valueToInject; + private final Type injectedType; + private final Supplier valueToInject; + + public static InjectionPoint injectedByAnnotation(Class annotation, Supplier valueToInject) { + return new InjectionPoint(annotation, determineTypeFromAnnotation(annotation), valueToInject); + } + + public static InjectionPoint injectedByAnnotation(Class annotation, Type injectedType, Supplier valueToInject) { + return new InjectionPoint(annotation, injectedType, valueToInject); + } + + public static InjectionPoint injectedByType(Class injectedType, Supplier valueToInject) { + return new InjectionPoint(null, injectedType, valueToInject); + } - public InjectionPoint(Class annotation, Class injectedType, Object valueToInject) { + private InjectionPoint(@Nullable Class annotation, Type injectedType, Supplier valueToInject) { this.annotation = annotation; this.injectedType = injectedType; this.valueToInject = valueToInject; } - public InjectionPoint(Class annotation, Object valueToInject) { - this(annotation, determineTypeFromAnnotation(annotation), valueToInject); - } - private static Class determineTypeFromAnnotation(Class annotation) { Class[] supportedTypes = annotation.getAnnotation(InjectionPointQualifier.class).supportedTypes(); if (supportedTypes.length != 1) { @@ -389,35 +383,36 @@ private static Class determineTypeFromAnnotation(Class return supportedTypes[0]; } + @Nullable public Class getAnnotation() { return annotation; } - public Class getInjectedType() { + public Type getInjectedType() { return injectedType; } public Object getValueToInject() { - return valueToInject; + return valueToInject.get(); } } } - private static class IsolatableParameters { - private HashCode secondaryInputsHash; - private Isolatable isolatableParameters; + private static class IsolatedParameters { + private final HashCode secondaryInputsHash; + private final Isolatable isolatedParameterObject; - public IsolatableParameters(Isolatable isolatableParameters, HashCode secondaryInputsHash) { + public IsolatedParameters(Isolatable isolatedParameterObject, HashCode secondaryInputsHash) { this.secondaryInputsHash = secondaryInputsHash; - this.isolatableParameters = isolatableParameters; + this.isolatedParameterObject = isolatedParameterObject; } public HashCode getSecondaryInputsHash() { return secondaryInputsHash; } - public Isolatable getIsolatableParameters() { - return isolatableParameters; + public Isolatable getIsolatedParameterObject() { + return isolatedParameterObject; } } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformerInvoker.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformerInvoker.java index 792dcaa61dbc6..e9ed746f4b845 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformerInvoker.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultTransformerInvoker.java @@ -20,32 +20,30 @@ import com.google.common.collect.ImmutableSortedMap; import org.gradle.api.UncheckedIOException; import org.gradle.api.artifacts.component.ProjectComponentIdentifier; +import org.gradle.api.file.FileSystemLocation; import org.gradle.api.file.RelativePath; import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder; import org.gradle.api.internal.artifacts.transform.TransformationWorkspaceProvider.TransformationWorkspace; import org.gradle.api.internal.file.FileCollectionFactory; import org.gradle.api.internal.project.ProjectInternal; -import org.gradle.caching.BuildCacheKey; -import org.gradle.caching.internal.origin.OriginMetadata; +import org.gradle.api.internal.provider.Providers; +import org.gradle.api.provider.Provider; import org.gradle.internal.Try; import org.gradle.internal.UncheckedException; -import org.gradle.internal.change.Change; -import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.SummarizingChangeContainer; import org.gradle.internal.classloader.ClassLoaderHierarchyHasher; -import org.gradle.internal.execution.CacheHandler; -import org.gradle.internal.execution.ExecutionOutcome; +import org.gradle.internal.execution.CachingResult; +import org.gradle.internal.execution.IncrementalContext; import org.gradle.internal.execution.UnitOfWork; import org.gradle.internal.execution.WorkExecutor; +import org.gradle.internal.execution.caching.CachingDisabledReason; +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory; import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import org.gradle.internal.execution.history.BeforeExecutionState; import org.gradle.internal.execution.history.ExecutionHistoryStore; -import org.gradle.internal.execution.history.changes.AbstractFingerprintChanges; -import org.gradle.internal.execution.history.changes.ExecutionStateChanges; -import org.gradle.internal.execution.history.changes.InputFileChanges; -import org.gradle.internal.execution.impl.steps.UpToDateResult; +import org.gradle.internal.execution.history.changes.InputChangesInternal; +import org.gradle.internal.execution.history.impl.DefaultBeforeExecutionState; import org.gradle.internal.file.TreeType; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.fingerprint.FileCollectionFingerprint; import org.gradle.internal.fingerprint.FileCollectionFingerprinter; import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry; import org.gradle.internal.fingerprint.OutputNormalizer; @@ -68,76 +66,111 @@ import java.nio.file.Path; import java.time.Duration; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; public class DefaultTransformerInvoker implements TransformerInvoker { + private static final CachingDisabledReason NOT_CACHEABLE = new CachingDisabledReason(CachingDisabledReasonCategory.NOT_CACHEABLE, "Caching not enabled."); + private static final String INPUT_ARTIFACT_PROPERTY_NAME = "inputArtifact"; + private static final String DEPENDENCIES_PROPERTY_NAME = "inputArtifactDependencies"; + private static final String SECONDARY_INPUTS_HASH_PROPERTY_NAME = "inputPropertiesHash"; + private static final String OUTPUT_DIRECTORY_PROPERTY_NAME = "outputDirectory"; + private static final String RESULTS_FILE_PROPERTY_NAME = "resultsFile"; + private static final String INPUT_FILE_PATH_PREFIX = "i/"; + private static final String OUTPUT_FILE_PATH_PREFIX = "o/"; private final FileSystemSnapshotter fileSystemSnapshotter; - private final WorkExecutor workExecutor; + private final WorkExecutor workExecutor; private final ArtifactTransformListener artifactTransformListener; private final CachingTransformationWorkspaceProvider immutableTransformationWorkspaceProvider; - private final FileCollectionFingerprinterRegistry fileCollectionFingerprinterRegistry; private final FileCollectionFactory fileCollectionFactory; - private final FileCollectionFingerprinter outputFingerprinter; private final ClassLoaderHierarchyHasher classLoaderHierarchyHasher; private final ProjectFinder projectFinder; - private final boolean useTransformationWorkspaces; - public DefaultTransformerInvoker(WorkExecutor workExecutor, + public DefaultTransformerInvoker(WorkExecutor workExecutor, FileSystemSnapshotter fileSystemSnapshotter, ArtifactTransformListener artifactTransformListener, CachingTransformationWorkspaceProvider immutableTransformationWorkspaceProvider, - FileCollectionFingerprinterRegistry fileCollectionFingerprinterRegistry, FileCollectionFactory fileCollectionFactory, ClassLoaderHierarchyHasher classLoaderHierarchyHasher, - ProjectFinder projectFinder, - boolean useTransformationWorkspaces) { + ProjectFinder projectFinder) { this.workExecutor = workExecutor; this.fileSystemSnapshotter = fileSystemSnapshotter; this.artifactTransformListener = artifactTransformListener; this.immutableTransformationWorkspaceProvider = immutableTransformationWorkspaceProvider; - this.fileCollectionFingerprinterRegistry = fileCollectionFingerprinterRegistry; this.fileCollectionFactory = fileCollectionFactory; this.classLoaderHierarchyHasher = classLoaderHierarchyHasher; this.projectFinder = projectFinder; - this.useTransformationWorkspaces = useTransformationWorkspaces; - outputFingerprinter = fileCollectionFingerprinterRegistry.getFingerprinter(OutputNormalizer.class); } @Override - public Try> invoke(Transformer transformer, File inputArtifact, ArtifactTransformDependencies dependencies, TransformationSubject subject) { - FileCollectionFingerprinter dependencyFingerprinter = fileCollectionFingerprinterRegistry.getFingerprinter(transformer.getInputArtifactDependenciesNormalizer()); + public Try> invoke(Transformer transformer, File inputArtifact, ArtifactTransformDependencies dependencies, TransformationSubject subject, FileCollectionFingerprinterRegistry fingerprinterRegistry) { + FileCollectionFingerprinter dependencyFingerprinter = fingerprinterRegistry.getFingerprinter(transformer.getInputArtifactDependenciesNormalizer()); CurrentFileCollectionFingerprint dependenciesFingerprint = dependencies.fingerprint(dependencyFingerprinter); ProjectInternal producerProject = determineProducerProject(subject); CachingTransformationWorkspaceProvider workspaceProvider = determineWorkspaceProvider(producerProject); FileSystemLocationSnapshot inputArtifactSnapshot = fileSystemSnapshotter.snapshot(inputArtifact); - FileCollectionFingerprinter inputArtifactFingerprinter = fileCollectionFingerprinterRegistry.getFingerprinter(transformer.getInputArtifactNormalizer()); + FileCollectionFingerprinter inputArtifactFingerprinter = fingerprinterRegistry.getFingerprinter(transformer.getInputArtifactNormalizer()); String normalizedInputPath = inputArtifactFingerprinter.normalizePath(inputArtifactSnapshot); TransformationWorkspaceIdentity identity = getTransformationIdentity(producerProject, inputArtifactSnapshot, normalizedInputPath, transformer, dependenciesFingerprint); return workspaceProvider.withWorkspace(identity, (identityString, workspace) -> { return fireTransformListeners(transformer, subject, () -> { + String transformIdentity = "transform/" + identityString; + ExecutionHistoryStore executionHistoryStore = workspaceProvider.getExecutionHistoryStore(); + FileCollectionFingerprinter outputFingerprinter = fingerprinterRegistry.getFingerprinter(OutputNormalizer.class); + + Optional afterPreviousExecutionState = executionHistoryStore.load(transformIdentity); + ImplementationSnapshot implementationSnapshot = ImplementationSnapshot.of(transformer.getImplementationClass(), classLoaderHierarchyHasher); CurrentFileCollectionFingerprint inputArtifactFingerprint = inputArtifactFingerprinter.fingerprint(ImmutableList.of(inputArtifactSnapshot)); + ImmutableSortedMap inputFingerprints = snapshotInputs(transformer); + ImmutableSortedMap outputsBeforeExecution = snapshotOutputs(outputFingerprinter, fileCollectionFactory, workspace); + ImmutableSortedMap inputFileFingerprints = createInputFileFingerprints(inputArtifactFingerprint, dependenciesFingerprint); + BeforeExecutionState beforeExecutionState = new DefaultBeforeExecutionState( + implementationSnapshot, + ImmutableList.of(), + inputFingerprints, + inputFileFingerprints, + outputsBeforeExecution + ); + TransformerExecution execution = new TransformerExecution( transformer, - implementationSnapshot, workspace, - identityString, - workspaceProvider.getExecutionHistoryStore(), + transformIdentity, + executionHistoryStore, fileCollectionFactory, inputArtifact, - inputArtifactFingerprint, dependencies, - dependenciesFingerprint, outputFingerprinter ); - UpToDateResult outcome = workExecutor.execute(execution); - return execution.getResult(outcome); + + CachingResult outcome = workExecutor.execute(new IncrementalContext() { + @Override + public UnitOfWork getWork() { + return execution; + } + + @Override + public Optional getRebuildReason() { + return Optional.empty(); + } + + @Override + public Optional getAfterPreviousExecutionState() { + return afterPreviousExecutionState; + } + + @Override + public Optional getBeforeExecutionState() { + return Optional.of(beforeExecutionState); + } + }); + + return outcome.getOutcome() + .map(outcome1 -> execution.loadResultsFile()) + .mapFailure(failure -> new TransformException(String.format("Execution failed for %s.", execution.getDisplayName()), failure)); }); }); } @@ -174,7 +207,7 @@ private CachingTransformationWorkspaceProvider determineWorkspaceProvider(@Nulla @Nullable private ProjectInternal determineProducerProject(TransformationSubject subject) { - if (!useTransformationWorkspaces || !subject.getProducer().isPresent()) { + if (!subject.getProducer().isPresent()) { return null; } ProjectComponentIdentifier projectComponentIdentifier = subject.getProducer().get(); @@ -191,76 +224,62 @@ private Try> fireTransformListeners(Transformer transformer, } private static class TransformerExecution implements UnitOfWork { - private static final String INPUT_ARTIFACT_PROPERTY_NAME = "inputArtifact"; - private static final String DEPENDENCIES_PROPERTY_NAME = "inputArtifactDependencies"; - private static final String SECONDARY_INPUTS_HASH_PROPERTY_NAME = "inputPropertiesHash"; - private static final String OUTPUT_DIRECTORY_PROPERTY_NAME = "outputDirectory"; - private static final String RESULTS_FILE_PROPERTY_NAME = "resultsFile"; - private static final String INPUT_FILE_PATH_PREFIX = "i/"; - private static final String OUTPUT_FILE_PATH_PREFIX = "o/"; - private final Transformer transformer; - private final ImplementationSnapshot implementationSnapshot; private final TransformationWorkspace workspace; private final File inputArtifact; private final String identityString; private final ExecutionHistoryStore executionHistoryStore; private final FileCollectionFactory fileCollectionFactory; private final ArtifactTransformDependencies dependencies; - private final ImmutableSortedMap inputSnapshots; - private final ImmutableSortedMap inputFileFingerprints; private final FileCollectionFingerprinter outputFingerprinter; private final Timer executionTimer; + private final Provider inputArtifactProvider; public TransformerExecution( Transformer transformer, - ImplementationSnapshot implementationSnapshot, TransformationWorkspace workspace, String identityString, ExecutionHistoryStore executionHistoryStore, FileCollectionFactory fileCollectionFactory, File inputArtifact, - CurrentFileCollectionFingerprint inputArtifactFingerprint, ArtifactTransformDependencies dependencies, - CurrentFileCollectionFingerprint dependenciesFingerprint, FileCollectionFingerprinter outputFingerprinter ) { - this.implementationSnapshot = implementationSnapshot; this.fileCollectionFactory = fileCollectionFactory; this.inputArtifact = inputArtifact; this.transformer = transformer; this.workspace = workspace; - this.identityString = "transform/" + identityString; + this.identityString = identityString; this.executionHistoryStore = executionHistoryStore; this.dependencies = dependencies; - this.inputSnapshots = ImmutableSortedMap.of( - // Emulate secondary inputs as a single property for now - SECONDARY_INPUTS_HASH_PROPERTY_NAME, ImplementationSnapshot.of("secondary inputs", transformer.getSecondaryInputHash()) - ); - this.inputFileFingerprints = createInputFileFingerprints(inputArtifactFingerprint, dependenciesFingerprint); this.outputFingerprinter = outputFingerprinter; this.executionTimer = Time.startTimer(); - } + this.inputArtifactProvider = Providers.of(new FileSystemLocation() { + @Override + public File getAsFile() { + return inputArtifact; + } - private static ImmutableSortedMap createInputFileFingerprints( - CurrentFileCollectionFingerprint inputArtifactFingerprint, - CurrentFileCollectionFingerprint dependenciesFingerprint - ) { - ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); - builder.put(INPUT_ARTIFACT_PROPERTY_NAME, inputArtifactFingerprint); - builder.put(DEPENDENCIES_PROPERTY_NAME, dependenciesFingerprint); - return builder.build(); + @Override + public String toString() { + return inputArtifact.toString(); + } + }); } @Override - public ExecutionOutcome execute() { + public WorkResult execute(@Nullable InputChangesInternal inputChanges) { File outputDir = workspace.getOutputDirectory(); File resultsFile = workspace.getResultsFile(); - GFileUtils.cleanDirectory(outputDir); - GFileUtils.deleteFileQuietly(resultsFile); - ImmutableList result = transformer.transform(inputArtifact, outputDir, dependencies); + + boolean incremental = inputChanges != null && inputChanges.isIncremental(); + if (!incremental) { + GFileUtils.cleanDirectory(outputDir); + GFileUtils.deleteFileQuietly(resultsFile); + } + ImmutableList result = transformer.transform(inputArtifactProvider, outputDir, dependencies, inputChanges); writeResultsFile(outputDir, resultsFile, result); - return ExecutionOutcome.EXECUTED; + return WorkResult.DID_WORK; } private void writeResultsFile(File outputDir, File resultsFile, ImmutableList result) { @@ -285,11 +304,6 @@ private void writeResultsFile(File outputDir, File resultsFile, ImmutableList Files.write(resultsFile.toPath(), (Iterable) relativePaths::iterator)); } - private Try> getResult(UpToDateResult result) { - return result.getOutcome() - .map(outcome -> loadResultsFile()); - } - private ImmutableList loadResultsFile() { Path transformerResultsPath = workspace.getResultsFile().toPath(); try { @@ -310,84 +324,51 @@ private ImmutableList loadResultsFile() { } } + @Override + public ExecutionHistoryStore getExecutionHistoryStore() { + return executionHistoryStore; + } + @Override public Optional getTimeout() { return Optional.empty(); } @Override - public void visitOutputProperties(OutputPropertyVisitor visitor) { - visitor.visitOutputProperty(OUTPUT_DIRECTORY_PROPERTY_NAME, TreeType.DIRECTORY, fileCollectionFactory.fixed(workspace.getOutputDirectory())); - visitor.visitOutputProperty(RESULTS_FILE_PROPERTY_NAME, TreeType.FILE, fileCollectionFactory.fixed(workspace.getResultsFile())); + public void visitInputFileProperties(InputFilePropertyVisitor visitor) { + visitor.visitInputFileProperty(INPUT_ARTIFACT_PROPERTY_NAME, inputArtifactProvider, true); } @Override - public long markExecutionTime() { - return executionTimer.getElapsedMillis(); + public void visitOutputProperties(OutputPropertyVisitor visitor) { + visitor.visitOutputProperty(OUTPUT_DIRECTORY_PROPERTY_NAME, TreeType.DIRECTORY, fileCollectionFactory.fixed(workspace.getOutputDirectory())); + visitor.visitOutputProperty(RESULTS_FILE_PROPERTY_NAME, TreeType.FILE, fileCollectionFactory.fixed(workspace.getResultsFile())); } @Override - public void visitLocalState(LocalStateVisitor visitor) { + public boolean isAllowOverlappingOutputs() { + return false; } @Override - public void outputsRemovedAfterFailureToLoadFromCache() { + public long markExecutionTime() { + return executionTimer.getElapsedMillis(); } @Override - public CacheHandler createCacheHandler() { - Hasher hasher = Hashing.newHasher(); - for (Map.Entry entry : inputSnapshots.entrySet()) { - hasher.putString(entry.getKey()); - entry.getValue().appendToHasher(hasher); - } - for (Map.Entry entry : inputFileFingerprints.entrySet()) { - hasher.putString(entry.getKey()); - hasher.putHash(entry.getValue().getHash()); - } - TransformerExecutionBuildCacheKey cacheKey = new TransformerExecutionBuildCacheKey(hasher.hash()); - return new CacheHandler() { - @Override - public Optional load(Function loader) { - if (!transformer.isCacheable()) { - return Optional.empty(); - } - return Optional.ofNullable(loader.apply(cacheKey)); - } - - @Override - public void store(Consumer storer) { - if (transformer.isCacheable()) { - storer.accept(cacheKey); - } - } - }; + public void visitLocalState(LocalStateVisitor visitor) { } @Override - public void persistResult(ImmutableSortedMap finalOutputs, boolean successful, OriginMetadata originMetadata) { - if (successful) { - executionHistoryStore.store( - identityString, - originMetadata, - implementationSnapshot, - ImmutableList.of(), - inputSnapshots, - inputFileFingerprints, - finalOutputs, - successful - ); - } + public Optional shouldDisableCaching() { + return transformer.isCacheable() + ? Optional.empty() + : Optional.of(NOT_CACHEABLE); } @Override - public Optional getChangesSincePreviousExecution() { - return executionHistoryStore.load(identityString).map(previous -> { - ImmutableSortedMap outputsBeforeExecution = snapshotOutputs(); - InputFileChanges inputFileChanges = new InputFileChanges(previous.getInputFileProperties(), inputFileFingerprints); - AllOutputFileChanges outputFileChanges = new AllOutputFileChanges(previous.getOutputFileProperties(), outputsBeforeExecution); - return new TransformerExecutionStateChanges(inputFileChanges, outputFileChanges, previous); - }); + public boolean isAllowedToLoadFromCache() { + return true; } @Override @@ -398,15 +379,17 @@ public Optional> getChangingOutputs() { @Override public ImmutableSortedMap snapshotAfterOutputsGenerated() { - return snapshotOutputs(); + return snapshotOutputs(outputFingerprinter, fileCollectionFactory, workspace); } - private ImmutableSortedMap snapshotOutputs() { - CurrentFileCollectionFingerprint outputFingerprint = outputFingerprinter.fingerprint(fileCollectionFactory.fixed(workspace.getOutputDirectory())); - CurrentFileCollectionFingerprint resultsFileFingerprint = outputFingerprinter.fingerprint(fileCollectionFactory.fixed(workspace.getResultsFile())); - return ImmutableSortedMap.of( - OUTPUT_DIRECTORY_PROPERTY_NAME, outputFingerprint, - RESULTS_FILE_PROPERTY_NAME, resultsFileFingerprint); + @Override + public boolean isRequiresInputChanges() { + return transformer.requiresInputChanges(); + } + + @Override + public boolean isRequiresLegacyInputChanges() { + return false; } @Override @@ -424,68 +407,31 @@ public void visitOutputTrees(CacheableTreeVisitor visitor) { public String getDisplayName() { return transformer.getDisplayName() + ": " + inputArtifact; } - - private class TransformerExecutionStateChanges implements ExecutionStateChanges { - private final InputFileChanges inputFileChanges; - private final AllOutputFileChanges outputFileChanges; - private final AfterPreviousExecutionState afterPreviousExecutionState; - - public TransformerExecutionStateChanges(InputFileChanges inputFileChanges, AllOutputFileChanges outputFileChanges, AfterPreviousExecutionState afterPreviousExecutionState) { - this.inputFileChanges = inputFileChanges; - this.outputFileChanges = outputFileChanges; - this.afterPreviousExecutionState = afterPreviousExecutionState; - } - - @Override - public Iterable getInputFilesChanges() { - return ImmutableList.of(); - } - - @Override - public void visitAllChanges(ChangeVisitor visitor) { - new SummarizingChangeContainer(inputFileChanges, outputFileChanges).accept(visitor); - } - - @Override - public boolean isRebuildRequired() { - return true; - } - - @Override - public AfterPreviousExecutionState getPreviousExecution() { - return afterPreviousExecutionState; - } - } - - private class TransformerExecutionBuildCacheKey implements BuildCacheKey { - private final HashCode hashCode; - - public TransformerExecutionBuildCacheKey(HashCode hashCode) { - this.hashCode = hashCode; - } - - @Override - public String getHashCode() { - return hashCode.toString(); - } - - @Override - public String getDisplayName() { - return getHashCode() + " for transformer " + TransformerExecution.this.getDisplayName(); - } - } } - private static class AllOutputFileChanges extends AbstractFingerprintChanges { + private static ImmutableSortedMap createInputFileFingerprints( + CurrentFileCollectionFingerprint inputArtifactFingerprint, + CurrentFileCollectionFingerprint dependenciesFingerprint + ) { + ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); + builder.put(INPUT_ARTIFACT_PROPERTY_NAME, inputArtifactFingerprint); + builder.put(DEPENDENCIES_PROPERTY_NAME, dependenciesFingerprint); + return builder.build(); + } - public AllOutputFileChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { - super(previous, current, "Output"); - } + private static ImmutableSortedMap snapshotInputs(Transformer transformer) { + return ImmutableSortedMap.of( + // Emulate secondary inputs as a single property for now + SECONDARY_INPUTS_HASH_PROPERTY_NAME, ImplementationSnapshot.of("secondary inputs", transformer.getSecondaryInputHash()) + ); + } - @Override - public boolean accept(ChangeVisitor visitor) { - return accept(visitor, true); - } + private static ImmutableSortedMap snapshotOutputs(FileCollectionFingerprinter outputFingerprinter, FileCollectionFactory fileCollectionFactory, TransformationWorkspace workspace) { + CurrentFileCollectionFingerprint outputFingerprint = outputFingerprinter.fingerprint(fileCollectionFactory.fixed(workspace.getOutputDirectory())); + CurrentFileCollectionFingerprint resultsFileFingerprint = outputFingerprinter.fingerprint(fileCollectionFactory.fixed(workspace.getResultsFile())); + return ImmutableSortedMap.of( + OUTPUT_DIRECTORY_PROPERTY_NAME, outputFingerprint, + RESULTS_FILE_PROPERTY_NAME, resultsFileFingerprint); } private static class ImmutableTransformationWorkspaceIdentity implements TransformationWorkspaceIdentity { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultVariantTransformRegistry.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultVariantTransformRegistry.java index 6ccc6d3105ac8..cd631e2c4eaf7 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultVariantTransformRegistry.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DefaultVariantTransformRegistry.java @@ -17,13 +17,13 @@ package org.gradle.api.internal.artifacts.transform; import com.google.common.collect.Lists; +import com.google.common.reflect.TypeToken; import org.gradle.api.Action; import org.gradle.api.ActionConfiguration; import org.gradle.api.NonExtensible; import org.gradle.api.artifacts.transform.ArtifactTransform; -import org.gradle.api.artifacts.transform.AssociatedTransformAction; -import org.gradle.api.artifacts.transform.ParameterizedTransformSpec; import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.api.artifacts.transform.TransformSpec; import org.gradle.api.artifacts.transform.VariantTransform; import org.gradle.api.artifacts.transform.VariantTransformConfigurationException; @@ -40,6 +40,7 @@ import org.gradle.model.internal.type.ModelType; import javax.annotation.Nullable; +import java.lang.reflect.ParameterizedType; import java.util.Arrays; import java.util.List; @@ -78,23 +79,20 @@ public void registerTransform(Action registrationActio } @Override - public void registerTransform(Class parameterType, Action> registrationAction) { - T parameterObject = parametersInstantiationScheme.withServices(services).newInstance(parameterType); + public void registerTransform(Class> actionType, Action> registrationAction) { + ParameterizedType superType = (ParameterizedType) TypeToken.of(actionType).getSupertype(TransformAction.class).getType(); + Class parameterType = Cast.uncheckedNonnullCast(TypeToken.of(superType.getActualTypeArguments()[0]).getRawType()); + if (parameterType == TransformParameters.class) { + throw new VariantTransformConfigurationException(String.format("Could not register transform: must use a sub-type of %s as parameter type. Use %s for transforms without parameters.", ModelType.of(TransformParameters.class).getDisplayName(), ModelType.of(TransformParameters.None.class).getDisplayName())); + } + T parameterObject = parameterType == TransformParameters.None.class ? null : parametersInstantiationScheme.withServices(services).newInstance(parameterType); TypedRegistration registration = Cast.uncheckedNonnullCast(instantiatorFactory.decorateLenient().newInstance(TypedRegistration.class, parameterObject, immutableAttributesFactory)); registrationAction.execute(registration); - register(registration, registration.actionType, parameterObject); - } - - @Override - public void registerTransformAction(Class actionType, Action registrationAction) { - ActionRegistration registration = instantiatorFactory.decorateLenient().newInstance(ActionRegistration.class, immutableAttributesFactory); - registrationAction.execute(registration); - - register(registration, actionType, null); + register(registration, actionType, parameterObject); } - private void register(RecordingRegistration registration, Class actionType, @Nullable T parameterObject) { + private void register(RecordingRegistration registration, Class actionType, @Nullable T parameterObject) { validateActionType(actionType); validateAttributes(registration); try { @@ -105,13 +103,13 @@ private void register(RecordingRegistration registration, Class void validateActionType(@Nullable Class actionType) { + private static void validateActionType(@Nullable Class actionType) { if (actionType == null) { throw new VariantTransformConfigurationException("Could not register transform: an artifact transform action must be provided."); } } - private void validateAttributes(RecordingRegistration registration) { + private static void validateAttributes(RecordingRegistration registration) { if (registration.to.isEmpty()) { throw new VariantTransformConfigurationException("Could not register transform: at least one 'to' attribute must be provided."); } @@ -127,7 +125,7 @@ public Iterable getTransforms() { return transforms; } - public static abstract class RecordingRegistration implements TransformSpec { + public static abstract class RecordingRegistration { final AttributeContainerInternal from; final AttributeContainerInternal to; @@ -181,35 +179,28 @@ Object[] getTransformParameters() { } @NonExtensible - public static class TypedRegistration extends RecordingRegistration implements ParameterizedTransformSpec { + public static class TypedRegistration extends RecordingRegistration implements TransformSpec { private final T parameterObject; - Class actionType; - public TypedRegistration(T parameterObject, ImmutableAttributesFactory immutableAttributesFactory) { + public TypedRegistration(@Nullable T parameterObject, ImmutableAttributesFactory immutableAttributesFactory) { super(immutableAttributesFactory); this.parameterObject = parameterObject; - AssociatedTransformAction associatedTransformAction = parameterObject.getClass().getAnnotation(AssociatedTransformAction.class); - if (associatedTransformAction != null) { - actionType = associatedTransformAction.value(); - } } @Override public T getParameters() { + if (parameterObject == null) { + throw new VariantTransformConfigurationException("Cannot query parameters for artifact transform without parameters."); + } return parameterObject; } @Override public void parameters(Action action) { + if (parameterObject == null) { + throw new VariantTransformConfigurationException("Cannot configure parameters for artifact transform without parameters."); + } action.execute(parameterObject); } } - - @NonExtensible - public static class ActionRegistration extends RecordingRegistration { - - public ActionRegistration(ImmutableAttributesFactory immutableAttributesFactory) { - super(immutableAttributesFactory); - } - } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DomainObjectProjectStateHandler.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DomainObjectProjectStateHandler.java index 7af30f6bc7525..17e70bc8b0641 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DomainObjectProjectStateHandler.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/DomainObjectProjectStateHandler.java @@ -19,6 +19,7 @@ import org.gradle.api.Project; import org.gradle.api.internal.DomainObjectContext; import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder; +import org.gradle.api.internal.project.ProjectInternal; import org.gradle.api.internal.project.ProjectState; import org.gradle.api.internal.project.ProjectStateRegistry; @@ -37,7 +38,7 @@ public DomainObjectProjectStateHandler(ProjectStateRegistry projectStateRegistry } @Nullable - public Project maybeGetOwningProject() { + public ProjectInternal maybeGetOwningProject() { if (domainObjectContext.getProjectPath() != null) { return projectFinder.findProject(domainObjectContext.getProjectPath().getPath()); } else { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/InputArtifactAnnotationHandler.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/InputArtifactAnnotationHandler.java index b1afbc7d0b652..4df6db7de48bc 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/InputArtifactAnnotationHandler.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/InputArtifactAnnotationHandler.java @@ -29,11 +29,6 @@ public Class getAnnotationType() { return InputArtifact.class; } - @Override - public Class getAnnotation() { - return InputArtifact.class; - } - @Override protected InputFilePropertyType getFilePropertyType() { return InputFilePropertyType.FILE; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/InputArtifactDependenciesAnnotationHandler.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/InputArtifactDependenciesAnnotationHandler.java index 82715f2075f5b..7a9c2015aa779 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/InputArtifactDependenciesAnnotationHandler.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/InputArtifactDependenciesAnnotationHandler.java @@ -29,11 +29,6 @@ public Class getAnnotationType() { return InputArtifactDependencies.class; } - @Override - public Class getAnnotation() { - return InputArtifactDependencies.class; - } - @Override protected InputFilePropertyType getFilePropertyType() { return InputFilePropertyType.FILES; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/LegacyTransformer.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/LegacyTransformer.java index 521cf02b80d91..4f6d5ea606c9d 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/LegacyTransformer.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/LegacyTransformer.java @@ -19,11 +19,14 @@ import com.google.common.collect.ImmutableList; import org.gradle.api.InvalidUserDataException; import org.gradle.api.artifacts.transform.ArtifactTransform; +import org.gradle.api.file.FileSystemLocation; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.api.internal.tasks.TaskDependencyResolveContext; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.FileNormalizer; import org.gradle.internal.classloader.ClassLoaderHierarchyHasher; import org.gradle.internal.fingerprint.AbsolutePathInputNormalizer; +import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry; import org.gradle.internal.hash.HashCode; import org.gradle.internal.hash.Hasher; import org.gradle.internal.hash.Hashing; @@ -31,7 +34,9 @@ import org.gradle.internal.isolation.Isolatable; import org.gradle.internal.isolation.IsolatableFactory; import org.gradle.internal.reflect.Instantiator; +import org.gradle.work.InputChanges; +import javax.annotation.Nullable; import java.io.File; import java.util.List; @@ -52,13 +57,19 @@ public boolean requiresDependencies() { return false; } + @Override + public boolean requiresInputChanges() { + return false; + } + @Override public boolean isCacheable() { return false; } @Override - public ImmutableList transform(File inputArtifact, File outputDir, ArtifactTransformDependencies dependencies) { + public ImmutableList transform(Provider inputArtifactProvider, File outputDir, ArtifactTransformDependencies dependencies, @Nullable InputChanges inputChanges) { + File inputArtifact = inputArtifactProvider.get().getAsFile(); ArtifactTransform transformer = newTransformer(); transformer.setOutputDirectory(outputDir); List outputs = transformer.transform(inputArtifact); @@ -93,12 +104,17 @@ public Class getInputArtifactDependenciesNormalizer() return AbsolutePathInputNormalizer.class; } + @Override + public boolean isIsolated() { + return true; + } + @Override public void visitDependencies(TaskDependencyResolveContext context) { } @Override - public void isolateParameters() { + public void isolateParameters(FileCollectionFingerprinterRegistry fingerprinterRegistry) { } private ArtifactTransform newTransformer() { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/PrecomputedTransformationResult.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/PrecomputedTransformationResult.java new file mode 100644 index 0000000000000..b919bae4b0e5d --- /dev/null +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/PrecomputedTransformationResult.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.artifacts.transform; + +import org.gradle.internal.Try; + +public class PrecomputedTransformationResult implements TransformationResult { + private final Try transformedSubject; + + public PrecomputedTransformationResult(Try transformedSubject) { + this.transformedSubject = transformedSubject; + } + + @Override + public Try getTransformedSubject() { + return transformedSubject; + } +} diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformCompletion.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformCompletion.java index b3fad67256d50..87591ed69a9b5 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformCompletion.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformCompletion.java @@ -27,10 +27,10 @@ public class TransformCompletion implements ResolvedArtifactSet.Completion { private final AttributeContainerInternal attributes; private final ResolvedArtifactSet.Completion delegate; - private final Map artifactResults; - private final Map fileResults; + private final Map artifactResults; + private final Map fileResults; - public TransformCompletion(ResolvedArtifactSet.Completion delegate, AttributeContainerInternal attributes, Map artifactResults, Map fileResults) { + public TransformCompletion(ResolvedArtifactSet.Completion delegate, AttributeContainerInternal attributes, Map artifactResults, Map fileResults) { this.delegate = delegate; this.attributes = attributes; this.artifactResults = artifactResults; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/ExecutionException.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformException.java similarity index 62% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/ExecutionException.java rename to subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformException.java index 149feb7665df4..2c9a674ed6cf9 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/ExecutionException.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformException.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,15 @@ * limitations under the License. */ -package org.gradle.internal.execution; +package org.gradle.api.internal.artifacts.transform; import org.gradle.api.GradleException; import org.gradle.internal.exceptions.Contextual; -/** - * An {@code ExecutionException} is thrown when a unit of work fails to execute successfully. - */ @Contextual -public class ExecutionException extends GradleException { - public ExecutionException(UnitOfWork work, Throwable cause) { - super(String.format("Execution failed for %s.", work.getDisplayName()), cause); +public class TransformException extends GradleException { + + public TransformException(String message, Throwable cause) { + super(message, cause); } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/Transformation.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/Transformation.java index 9a7362e059d34..78fc06ffd93cb 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/Transformation.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/Transformation.java @@ -18,8 +18,11 @@ import org.gradle.api.Action; import org.gradle.api.Describable; +import org.gradle.execution.ProjectExecutionServiceRegistry; import org.gradle.internal.Try; +import javax.annotation.Nullable; + /** * The internal API equivalent of {@link org.gradle.api.artifacts.transform.ArtifactTransform}, which is also aware of our cache infrastructure. * @@ -34,7 +37,7 @@ public interface Transformation extends Describable { /** * Transforms the given input subject. May call the underlying transformer(s) or retrieve a cached value. */ - Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver); + Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver, @Nullable ProjectExecutionServiceRegistry services); /** * Whether the transformation requires dependencies of the transformed artifact to be injected. diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationChain.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationChain.java index 1aa66fc8cf815..2d4bec5ffe840 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationChain.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationChain.java @@ -17,8 +17,11 @@ package org.gradle.api.internal.artifacts.transform; import org.gradle.api.Action; +import org.gradle.execution.ProjectExecutionServiceRegistry; import org.gradle.internal.Try; +import javax.annotation.Nullable; + /** * A series of {@link TransformationStep}s. */ @@ -57,9 +60,9 @@ public int stepsCount() { } @Override - public Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver) { - return first.transform(subjectToTransform, dependenciesResolver) - .flatMap(intermediateSubject -> second.transform(intermediateSubject, dependenciesResolver)); + public Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver, @Nullable ProjectExecutionServiceRegistry services) { + return first.transform(subjectToTransform, dependenciesResolver, services) + .flatMap(intermediateSubject -> second.transform(intermediateSubject, dependenciesResolver, services)); } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNode.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNode.java index f3d9bfa860e18..240e58c204485 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNode.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNode.java @@ -21,6 +21,7 @@ import org.gradle.api.artifacts.ResolveException; import org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvableArtifact; +import org.gradle.execution.ProjectExecutionServiceRegistry; import org.gradle.execution.plan.Node; import org.gradle.execution.plan.TaskDependencyResolver; import org.gradle.internal.Try; @@ -57,19 +58,24 @@ protected TransformationNode(TransformationStep transformationStep, ExecutionGra this.dependenciesResolver = dependenciesResolver; } - public abstract void execute(BuildOperationExecutor buildOperationExecutor, ArtifactTransformListener transformListener); + public abstract void execute(BuildOperationExecutor buildOperationExecutor, ArtifactTransformListener transformListener, ProjectExecutionServiceRegistry services); @Override public boolean isPublicNode() { return true; } + @Override + public boolean requiresMonitoring() { + return false; + } + @Override public String toString() { return transformationStep.getDisplayName(); } - private Try getTransformedSubject() { + public Try getTransformedSubject() { if (transformedSubject == null) { throw new IllegalStateException(String.format("Transformation %s has been scheduled and is now required, but did not execute, yet.", transformationStep.getDisplayName())); } @@ -135,7 +141,7 @@ public InitialTransformationNode(TransformationStep transformationStep, Resolvab } @Override - public void execute(BuildOperationExecutor buildOperationExecutor, ArtifactTransformListener transformListener) { + public void execute(BuildOperationExecutor buildOperationExecutor, ArtifactTransformListener transformListener, ProjectExecutionServiceRegistry services) { this.transformedSubject = buildOperationExecutor.call(new ArtifactTransformationStepBuildOperation() { @Override protected Try transform() { @@ -150,7 +156,7 @@ protected Try transform() { } TransformationSubject initialArtifactTransformationSubject = TransformationSubject.initial(artifact.getId(), file); - return transformationStep.transform(initialArtifactTransformationSubject, dependenciesResolver); + return transformationStep.transform(initialArtifactTransformationSubject, dependenciesResolver, services); } @Override @@ -177,12 +183,12 @@ public ChainedTransformationNode(TransformationStep transformationStep, Transfor } @Override - public void execute(BuildOperationExecutor buildOperationExecutor, ArtifactTransformListener transformListener) { + public void execute(BuildOperationExecutor buildOperationExecutor, ArtifactTransformListener transformListener, ProjectExecutionServiceRegistry services) { this.transformedSubject = buildOperationExecutor.call(new ArtifactTransformationStepBuildOperation() { @Override protected Try transform() { return previousTransformationNode.getTransformedSubject().flatMap(transformedSubject -> - transformationStep.transform(transformedSubject, dependenciesResolver)); + transformationStep.transform(transformedSubject, dependenciesResolver, services)); } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeDependencyResolver.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeDependencyResolver.java index 7ff7c41ed1636..0f8f0bdd4b194 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeDependencyResolver.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeDependencyResolver.java @@ -27,17 +27,17 @@ * Resolves dependencies to {@link TransformationNode} objects. */ public class TransformationNodeDependencyResolver implements DependencyResolver { - private final TransformationNodeFactory transformationNodeFactory; + private final TransformationNodeRegistry transformationNodeRegistry; - public TransformationNodeDependencyResolver(TransformationNodeFactory transformationNodeFactory) { - this.transformationNodeFactory = transformationNodeFactory; + public TransformationNodeDependencyResolver(TransformationNodeRegistry transformationNodeRegistry) { + this.transformationNodeRegistry = transformationNodeRegistry; } @Override public boolean resolve(Task task, Object node, Action resolveAction) { if (node instanceof DefaultTransformationDependency) { DefaultTransformationDependency transformation = (DefaultTransformationDependency) node; - Collection transformations = transformationNodeFactory.getOrCreate(transformation.getArtifacts(), transformation.getTransformation(), transformation.getDependenciesResolver()); + Collection transformations = transformationNodeRegistry.getOrCreate(transformation.getArtifacts(), transformation.getTransformation(), transformation.getDependenciesResolver()); for (TransformationNode transformationNode : transformations) { resolveAction.execute(transformationNode); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeExecutor.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeExecutor.java index 9d8a0245da0c5..d6c88eab4a7c3 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeExecutor.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeExecutor.java @@ -34,7 +34,7 @@ public TransformationNodeExecutor(BuildOperationExecutor buildOperationExecutor, public boolean execute(Node node, ProjectExecutionServiceRegistry services) { if (node instanceof TransformationNode) { TransformationNode transformationNode = (TransformationNode) node; - transformationNode.execute(buildOperationExecutor, transformListener); + transformationNode.execute(buildOperationExecutor, transformListener, services); return true; } else { return false; diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeRegistry.java similarity index 78% rename from subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeFactory.java rename to subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeRegistry.java index 6e32a6269caf3..11f7a71821467 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeFactory.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationNodeRegistry.java @@ -16,10 +16,14 @@ package org.gradle.api.internal.artifacts.transform; +import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvedArtifactSet; import java.util.Collection; +import java.util.Optional; -public interface TransformationNodeFactory { +public interface TransformationNodeRegistry { Collection getOrCreate(ResolvedArtifactSet artifactSet, Transformation transformation, ExecutionGraphDependenciesResolver dependenciesResolver); + + Optional getCompleted(ComponentArtifactIdentifier artifactId, Transformation transformation); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationOperation.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationOperation.java index f2b12cace28ac..b1faf0df7e945 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationOperation.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationOperation.java @@ -24,11 +24,11 @@ import javax.annotation.Nullable; -class TransformationOperation implements RunnableBuildOperation { +class TransformationOperation implements TransformationResult, RunnableBuildOperation { private final Transformation transformation; private final TransformationSubject subject; private final ExecutionGraphDependenciesResolver dependenciesResolver; - private Try result; + private Try transformedSubject; TransformationOperation(Transformation transformation, TransformationSubject subject, ExecutionGraphDependenciesResolver dependenciesResolver) { this.transformation = transformation; @@ -38,7 +38,7 @@ class TransformationOperation implements RunnableBuildOperation { @Override public void run(@Nullable BuildOperationContext context) { - result = transformation.transform(subject, dependenciesResolver); + transformedSubject = transformation.transform(subject, dependenciesResolver, null); } @Override @@ -49,7 +49,8 @@ public BuildOperationDescriptor.Builder description() { .operationType(BuildOperationCategory.UNCATEGORIZED); } - public Try getResult() { - return result; + @Override + public Try getTransformedSubject() { + return transformedSubject; } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationRegistrationFactory.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationRegistrationFactory.java index 082bde6fffdc3..a77b05efac028 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationRegistrationFactory.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationRegistrationFactory.java @@ -18,12 +18,13 @@ import org.gradle.api.artifacts.transform.ArtifactTransform; import org.gradle.api.artifacts.transform.TransformAction; +import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.api.internal.artifacts.ArtifactTransformRegistration; import org.gradle.api.internal.attributes.ImmutableAttributes; import javax.annotation.Nullable; public interface TransformationRegistrationFactory { - ArtifactTransformRegistration create(ImmutableAttributes from, ImmutableAttributes to, Class implementation, @Nullable Object parameterObject); + ArtifactTransformRegistration create(ImmutableAttributes from, ImmutableAttributes to, Class implementation, @Nullable TransformParameters parameterObject); ArtifactTransformRegistration create(ImmutableAttributes from, ImmutableAttributes to, Class implementation, Object[] params); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationResult.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationResult.java new file mode 100644 index 0000000000000..00b62f86b9148 --- /dev/null +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationResult.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.artifacts.transform; + +import org.gradle.internal.Try; + +public interface TransformationResult { + Try getTransformedSubject(); +} diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationStep.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationStep.java index f4c7cb786601d..b4cf6783c37f1 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationStep.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformationStep.java @@ -19,30 +19,63 @@ import com.google.common.base.Equivalence; import com.google.common.collect.ImmutableList; import org.gradle.api.Action; +import org.gradle.api.Project; import org.gradle.api.internal.attributes.ImmutableAttributes; +import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.api.internal.project.ProjectStateRegistry; import org.gradle.api.internal.tasks.TaskDependencyContainer; +import org.gradle.api.internal.tasks.TaskDependencyResolveContext; +import org.gradle.api.internal.tasks.WorkNodeAction; +import org.gradle.execution.ProjectExecutionServiceRegistry; +import org.gradle.internal.Cast; import org.gradle.internal.Try; +import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry; +import org.gradle.internal.service.ServiceRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import javax.annotation.Nullable; import java.io.File; +import java.util.concurrent.atomic.AtomicReference; /** * A single transformation step. * * Transforms a subject by invoking a transformer on each of the subjects files. */ -public class TransformationStep implements Transformation { +public class TransformationStep implements Transformation, TaskDependencyContainer { private static final Logger LOGGER = LoggerFactory.getLogger(TransformationStep.class); public static final Equivalence FOR_SCHEDULING = Equivalence.identity(); - private final Transformer transformer; private final TransformerInvoker transformerInvoker; - - public TransformationStep(Transformer transformer, TransformerInvoker transformerInvoker) { + private final DomainObjectProjectStateHandler projectStateHandler; + private final ProjectStateRegistry.SafeExclusiveLock isolationLock; + private final WorkNodeAction isolateAction; + private final ProjectInternal owningProject; + private final FileCollectionFingerprinterRegistry globalFingerprinterRegistry; + private final AtomicReference usedFingerprinterRegistry = new AtomicReference<>(); + + public TransformationStep(Transformer transformer, TransformerInvoker transformerInvoker, DomainObjectProjectStateHandler projectStateHandler, FileCollectionFingerprinterRegistry globalFingerprinterRegistry) { this.transformer = transformer; this.transformerInvoker = transformerInvoker; + this.projectStateHandler = projectStateHandler; + this.globalFingerprinterRegistry = globalFingerprinterRegistry; + this.isolationLock = projectStateHandler.newExclusiveOperationLock(); + this.owningProject = projectStateHandler.maybeGetOwningProject(); + this.isolateAction = transformer.isIsolated() ? null : new WorkNodeAction() { + @Nullable + @Override + public Project getProject() { + return owningProject; + } + + @Override + public void run(ServiceRegistry registry) { + FileCollectionFingerprinterRegistry fingerprinterRegistry = getFingerprinterRegistry(Cast.uncheckedCast(registry.find(FileCollectionFingerprinterRegistry.class))); + isolateExclusively(fingerprinterRegistry); + } + }; } @Override @@ -56,16 +89,19 @@ public int stepsCount() { } @Override - public Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver) { + public Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver, @Nullable ProjectExecutionServiceRegistry services) { if (LOGGER.isInfoEnabled()) { LOGGER.info("Transforming {} with {}", subjectToTransform.getDisplayName(), transformer.getDisplayName()); } ImmutableList inputArtifacts = subjectToTransform.getFiles(); - transformer.isolateParameters(); + FileCollectionFingerprinterRegistry fingerprinterRegistry = getFingerprinterRegistry( + owningProject != null && services != null ? services.getProjectService(owningProject, FileCollectionFingerprinterRegistry.class) : null + ); + isolateTransformerParameters(fingerprinterRegistry); return dependenciesResolver.forTransformer(transformer).flatMap(dependencies -> { ImmutableList.Builder builder = ImmutableList.builder(); for (File inputArtifact : inputArtifacts) { - Try> result = transformerInvoker.invoke(transformer, inputArtifact, dependencies, subjectToTransform); + Try> result = transformerInvoker.invoke(transformer, inputArtifact, dependencies, subjectToTransform, fingerprinterRegistry); if (result.getFailure().isPresent()) { return Try.failure(result.getFailure().get()); @@ -76,6 +112,29 @@ public Try transform(TransformationSubject subjectToTrans }); } + public FileCollectionFingerprinterRegistry getFingerprinterRegistry(@Nullable FileCollectionFingerprinterRegistry candidate) { + usedFingerprinterRegistry.compareAndSet(null, candidate == null ? globalFingerprinterRegistry : candidate); + return usedFingerprinterRegistry.get(); + } + + private void isolateTransformerParameters(FileCollectionFingerprinterRegistry fingerprinterRegistry) { + if (!transformer.isIsolated()) { + if (!projectStateHandler.hasMutableProjectState()) { + projectStateHandler.withLenientState(() -> isolateExclusively(fingerprinterRegistry)); + } else { + isolateExclusively(fingerprinterRegistry); + } + } + } + + private void isolateExclusively(FileCollectionFingerprinterRegistry fingerprinterRegistry) { + isolationLock.withLock(() -> { + if (!transformer.isIsolated()) { + transformer.isolateParameters(fingerprinterRegistry); + } + }); + } + @Override public boolean requiresDependencies() { return transformer.requiresDependencies(); @@ -103,4 +162,12 @@ public String toString() { public TaskDependencyContainer getDependencies() { return transformer; } + + @Override + public void visitDependencies(TaskDependencyResolveContext context) { + if (!transformer.isIsolated()) { + context.add(isolateAction); + } + transformer.visitDependencies(context); + } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/Transformer.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/Transformer.java index 96a4ac8384097..360405f66073e 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/Transformer.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/Transformer.java @@ -19,11 +19,16 @@ import com.google.common.collect.ImmutableList; import org.gradle.api.Describable; import org.gradle.api.artifacts.transform.ArtifactTransform; +import org.gradle.api.file.FileSystemLocation; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.api.internal.tasks.TaskDependencyContainer; +import org.gradle.api.provider.Provider; import org.gradle.api.tasks.FileNormalizer; +import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry; import org.gradle.internal.hash.HashCode; +import org.gradle.work.InputChanges; +import javax.annotation.Nullable; import java.io.File; /** @@ -41,12 +46,17 @@ public interface Transformer extends Describable, TaskDependencyContainer { */ boolean requiresDependencies(); + /** + * Whether the transformer requires {@link InputChanges} to be injected. + */ + boolean requiresInputChanges(); + /** * Whether the transformer is cacheable. */ boolean isCacheable(); - ImmutableList transform(File inputArtifact, File outputDir, ArtifactTransformDependencies dependencies); + ImmutableList transform(Provider inputArtifactProvider, File outputDir, ArtifactTransformDependencies dependencies, @Nullable InputChanges inputChanges); /** * The hash of the secondary inputs of the transformer. @@ -55,9 +65,11 @@ public interface Transformer extends Describable, TaskDependencyContainer { */ HashCode getSecondaryInputHash(); - void isolateParameters(); + void isolateParameters(FileCollectionFingerprinterRegistry fingerprinterRegistry); Class getInputArtifactNormalizer(); Class getInputArtifactDependenciesNormalizer(); + + boolean isIsolated(); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformerInvoker.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformerInvoker.java index 0f21497767913..83fc9c88b5183 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformerInvoker.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformerInvoker.java @@ -17,8 +17,9 @@ package org.gradle.api.internal.artifacts.transform; import com.google.common.collect.ImmutableList; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.Try; +import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry; import java.io.File; @@ -27,5 +28,5 @@ public interface TransformerInvoker { /** * Returns the result of applying the given transformer to the given file. */ - Try> invoke(Transformer transformer, File inputArtifact, ArtifactTransformDependencies dependencies, TransformationSubject subject); + Try> invoke(Transformer transformer, File inputArtifact, ArtifactTransformDependencies dependencies, TransformationSubject subject, FileCollectionFingerprinterRegistry fingerprinterRegistry); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformingArtifactVisitor.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformingArtifactVisitor.java index 765e8fd1ff535..b5d1b38432a3b 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformingArtifactVisitor.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformingArtifactVisitor.java @@ -18,7 +18,6 @@ import org.gradle.api.artifacts.ResolvedArtifact; import org.gradle.api.artifacts.component.ComponentArtifactIdentifier; -import org.gradle.api.artifacts.transform.ArtifactTransformException; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.internal.artifacts.DefaultResolvedArtifact; import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ArtifactVisitor; @@ -35,10 +34,10 @@ class TransformingArtifactVisitor implements ArtifactVisitor { private final ArtifactVisitor visitor; private final AttributeContainerInternal target; - private final Map artifactResults; - private final Map fileResults; + private final Map artifactResults; + private final Map fileResults; - TransformingArtifactVisitor(ArtifactVisitor visitor, AttributeContainerInternal target, Map artifactResults, Map fileResults) { + TransformingArtifactVisitor(ArtifactVisitor visitor, AttributeContainerInternal target, Map artifactResults, Map fileResults) { this.visitor = visitor; this.target = target; this.artifactResults = artifactResults; @@ -47,8 +46,8 @@ class TransformingArtifactVisitor implements ArtifactVisitor { @Override public void visitArtifact(DisplayName variantName, AttributeContainer variantAttributes, ResolvableArtifact artifact) { - TransformationOperation operation = artifactResults.get(artifact.getId()); - operation.getResult().ifSuccessfulOrElse( + TransformationResult result = artifactResults.get(artifact.getId()); + result.getTransformedSubject().ifSuccessfulOrElse( transformedSubject -> { ResolvedArtifact sourceArtifact = artifact.toPublicView(); for (File output : transformedSubject.getFiles()) { @@ -58,7 +57,9 @@ public void visitArtifact(DisplayName variantName, AttributeContainer variantAtt visitor.visitArtifact(variantName, target, resolvedArtifact); } }, - failure -> visitor.visitFailure(new ArtifactTransformException(artifact.getId(), target, failure)) + failure -> visitor.visitFailure( + new TransformException(String.format("Failed to transform artifact '%s' to match attributes %s.", + artifact.getId(), target), failure)) ); } @@ -79,14 +80,15 @@ public boolean requireArtifactFiles() { @Override public void visitFile(ComponentArtifactIdentifier artifactIdentifier, DisplayName variantName, AttributeContainer variantAttributes, File file) { - TransformationOperation operation = fileResults.get(file); - operation.getResult().ifSuccessfulOrElse( + TransformationResult result = fileResults.get(file); + result.getTransformedSubject().ifSuccessfulOrElse( transformedSubject -> { for (File outputFile : transformedSubject.getFiles()) { visitor.visitFile(new ComponentFileArtifactIdentifier(artifactIdentifier.getComponentIdentifier(), outputFile.getName()), variantName, target, outputFile); } }, - failure -> visitor.visitFailure(new ArtifactTransformException(file, target, failure)) + failure -> visitor.visitFailure(new TransformException(String.format("Failed to transform file '%s' to match attributes %s", + file.getName(), target), failure)) ); } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformingAsyncArtifactListener.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformingAsyncArtifactListener.java index ddf0c4255b75e..0e1e47fa66307 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformingAsyncArtifactListener.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/TransformingAsyncArtifactListener.java @@ -24,11 +24,13 @@ import java.io.File; import java.util.Map; +import java.util.Optional; class TransformingAsyncArtifactListener implements ResolvedArtifactSet.AsyncArtifactListener { - private final Map artifactResults; - private final Map fileResults; + private final Map artifactResults; + private final Map fileResults; private final ExecutionGraphDependenciesResolver dependenciesResolver; + private final TransformationNodeRegistry transformationNodeRegistry; private final BuildOperationQueue actions; private final ResolvedArtifactSet.AsyncArtifactListener delegate; private final Transformation transformation; @@ -37,9 +39,10 @@ class TransformingAsyncArtifactListener implements ResolvedArtifactSet.AsyncArti Transformation transformation, ResolvedArtifactSet.AsyncArtifactListener delegate, BuildOperationQueue actions, - Map artifactResults, - Map fileResults, - ExecutionGraphDependenciesResolver dependenciesResolver + Map artifactResults, + Map fileResults, + ExecutionGraphDependenciesResolver dependenciesResolver, + TransformationNodeRegistry transformationNodeRegistry ) { this.artifactResults = artifactResults; this.actions = actions; @@ -47,21 +50,32 @@ class TransformingAsyncArtifactListener implements ResolvedArtifactSet.AsyncArti this.delegate = delegate; this.fileResults = fileResults; this.dependenciesResolver = dependenciesResolver; + this.transformationNodeRegistry = transformationNodeRegistry; } @Override public void artifactAvailable(ResolvableArtifact artifact) { ComponentArtifactIdentifier artifactId = artifact.getId(); - File file = artifact.getFile(); - TransformationSubject initialSubject = TransformationSubject.initial(artifactId, file); - TransformationOperation operation = new TransformationOperation(transformation, initialSubject, dependenciesResolver); - artifactResults.put(artifactId, operation); - // We expect artifact transformations to be executed in a scheduled way, - // so at this point we take the result from the in-memory cache. - // Artifact transformations are always executed scheduled via the execution graph when the transformed component is declared as an input. - // Using the BuildOperationQueue here to only realize that the result of the transformation is from the in-memory has a performance impact, - // so we executing the (no-op) operation in place. - operation.run(null); + Optional node = transformationNodeRegistry.getCompleted(artifactId, transformation); + if (node.isPresent()) { + artifactResults.put(artifactId, new PrecomputedTransformationResult(node.get().getTransformedSubject())); + } else { + File file = artifact.getFile(); + TransformationSubject initialSubject = TransformationSubject.initial(artifactId, file); + TransformationOperation operation = new TransformationOperation(transformation, initialSubject, dependenciesResolver); + artifactResults.put(artifactId, operation); + // If we are here, then the transform has not been scheduled. + // So either + // 1) the transformed variant is not declared as an input for a work item or resolved at configuration time, or + // 2) the artifact to transform is an external artifact. + // For 1), we don't do any performance optimizations since transformed variants should be declared as input to some work. + // For 2), either the artifact has just been downloaded or it was already downloaded earlier. + // If it has just been downloaded, then, since downloads happen in parallel, we are already on a worker thread and we use it to execute the transform. + // If it has been downloaded earlier, then there is a high chance that the transformed artifact is already in a Gradle user home workspace and up-to-date. + // Using the BuildOperationQueue here to only realize that the result of the transformation is up-to-date in the Gradle user home workspace has a performance impact, + // so we are executing the up-to-date transform operation in place. + operation.run(null); + } } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/UnzipTransform.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/UnzipTransform.java index eee90b1f051f1..4272b6a0c1e18 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/UnzipTransform.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/artifacts/transform/UnzipTransform.java @@ -21,6 +21,7 @@ import org.gradle.api.artifacts.transform.InputArtifact; import org.gradle.api.artifacts.transform.TransformAction; import org.gradle.api.artifacts.transform.TransformOutputs; +import org.gradle.api.artifacts.transform.TransformParameters; import org.gradle.internal.UncheckedException; import java.io.BufferedInputStream; @@ -38,7 +39,7 @@ * is located in the output directory of the transform and is named after the zipped file name * minus the extension. */ -public interface UnzipTransform extends TransformAction { +public interface UnzipTransform extends TransformAction { @InputArtifact File getZippedFile(); diff --git a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/attributes/DefaultOrderedDisambiguationRule.java b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/attributes/DefaultOrderedDisambiguationRule.java index f99c7cc0c336d..00ddb81b628bd 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/api/internal/attributes/DefaultOrderedDisambiguationRule.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/api/internal/attributes/DefaultOrderedDisambiguationRule.java @@ -18,8 +18,8 @@ import org.gradle.api.Action; import org.gradle.api.attributes.MultipleCandidatesDetails; -import java.util.Collection; import java.util.Comparator; +import java.util.Set; public class DefaultOrderedDisambiguationRule implements Action> { private final Comparator comparator; @@ -32,22 +32,20 @@ public DefaultOrderedDisambiguationRule(Comparator comparator, boolea @Override public void execute(MultipleCandidatesDetails details) { - Collection values = details.getCandidateValues(); + Set candidateValues = details.getCandidateValues(); T min = null; T max = null; - for (T value : values) { - + for (T value : candidateValues) { if (min == null || comparator.compare(value, min) < 0) { min = value; } if (max == null || comparator.compare(value, max) > 0) { max = value; } - } T cmp = pickFirst ? min : max; if (cmp != null) { - for (T value : details.getCandidateValues()) { + for (T value : candidateValues) { if (value.equals(cmp)) { details.closestMatch(value); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractConfigurationMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractConfigurationMetadata.java index a28a72c1008ae..2be6559d63ba5 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractConfigurationMetadata.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractConfigurationMetadata.java @@ -176,5 +176,4 @@ protected ModuleComponentIdentifier getComponentId() { return componentId; } - protected abstract ConfigurationMetadata withAttributes(ImmutableAttributes attributes); } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractVariantBackedConfigurationMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractVariantBackedConfigurationMetadata.java index 29229fb29abd9..bc2fc37632a4f 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractVariantBackedConfigurationMetadata.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/AbstractVariantBackedConfigurationMetadata.java @@ -143,7 +143,7 @@ public CapabilitiesMetadata getCapabilities() { @Override public List getArtifacts() { - return ImmutableList.of(); + return variant.getArtifacts(); } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ConfigurationBoundExternalDependencyMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ConfigurationBoundExternalDependencyMetadata.java index add9f658f15e0..ffccd6d0cae4c 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ConfigurationBoundExternalDependencyMetadata.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/ConfigurationBoundExternalDependencyMetadata.java @@ -51,17 +51,22 @@ public class ConfigurationBoundExternalDependencyMetadata implements ModuleDepen private boolean alwaysUseAttributeMatching; - private ConfigurationBoundExternalDependencyMetadata(ConfigurationMetadata configuration, ModuleComponentIdentifier componentId, ExternalDependencyDescriptor dependencyDescriptor, String reason) { + private ConfigurationBoundExternalDependencyMetadata(ConfigurationMetadata configuration, ModuleComponentIdentifier componentId, ExternalDependencyDescriptor dependencyDescriptor, boolean alwaysUseAttributeMatching, String reason) { this.configuration = configuration; this.componentId = componentId; this.dependencyDescriptor = dependencyDescriptor; + this.alwaysUseAttributeMatching = alwaysUseAttributeMatching; this.reason = reason; this.isTransitive = dependencyDescriptor.isTransitive(); this.isConstraint = dependencyDescriptor.isConstraint(); } + private ConfigurationBoundExternalDependencyMetadata(ConfigurationMetadata configuration, ModuleComponentIdentifier componentId, ExternalDependencyDescriptor dependencyDescriptor, boolean alwaysUseAttributeMatching) { + this(configuration, componentId, dependencyDescriptor, alwaysUseAttributeMatching, null); + } + public ConfigurationBoundExternalDependencyMetadata(ConfigurationMetadata configuration, ModuleComponentIdentifier componentId, ExternalDependencyDescriptor dependencyDescriptor) { - this(configuration, componentId, dependencyDescriptor, null); + this(configuration, componentId, dependencyDescriptor, false, null); } public ConfigurationBoundExternalDependencyMetadata alwaysUseAttributeMatching() { @@ -84,7 +89,7 @@ public List selectConfigurations(ImmutableAttributes cons // This is a slight different condition than that used for a dependency declared in a Gradle project, // which is (targetHasVariants || consumerHasAttributes), relying on the fallback to 'default' for consumer attributes without any variants. if (alwaysUseAttributeMatching || hasVariants(targetComponent)) { - return ImmutableList.of(AttributeConfigurationSelector.selectConfigurationUsingAttributeMatching(consumerAttributes, explicitRequestedCapabilities, targetComponent, consumerSchema)); + return ImmutableList.of(AttributeConfigurationSelector.selectConfigurationUsingAttributeMatching(consumerAttributes, explicitRequestedCapabilities, targetComponent, consumerSchema, getArtifacts())); } return dependencyDescriptor.selectLegacyConfigurations(componentId, configuration, targetComponent); } @@ -134,16 +139,16 @@ public ModuleDependencyMetadata withReason(String reason) { if (Objects.equal(reason, this.getReason())) { return this; } - return new ConfigurationBoundExternalDependencyMetadata(configuration, componentId, dependencyDescriptor, reason); + return new ConfigurationBoundExternalDependencyMetadata(configuration, componentId, dependencyDescriptor, alwaysUseAttributeMatching, reason); } public ConfigurationBoundExternalDependencyMetadata withDescriptor(ExternalDependencyDescriptor descriptor) { - return new ConfigurationBoundExternalDependencyMetadata(configuration, componentId, descriptor); + return new ConfigurationBoundExternalDependencyMetadata(configuration, componentId, descriptor, alwaysUseAttributeMatching); } private ModuleDependencyMetadata withRequested(ModuleComponentSelector newSelector) { ExternalDependencyDescriptor newDelegate = dependencyDescriptor.withRequested(newSelector); - return new ConfigurationBoundExternalDependencyMetadata(configuration, componentId, newDelegate); + return new ConfigurationBoundExternalDependencyMetadata(configuration, componentId, newDelegate, alwaysUseAttributeMatching); } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultConfigurationMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultConfigurationMetadata.java index 64d1c68b127f5..27ecde618123b 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultConfigurationMetadata.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/DefaultConfigurationMetadata.java @@ -22,6 +22,7 @@ import org.gradle.api.capabilities.CapabilitiesMetadata; import org.gradle.api.capabilities.Capability; import org.gradle.api.internal.attributes.ImmutableAttributes; +import org.gradle.internal.Cast; import org.gradle.internal.Factory; import org.gradle.internal.component.model.DependencyMetadata; import org.gradle.internal.component.model.ExcludeMetadata; @@ -156,22 +157,10 @@ public CapabilitiesMetadata getCapabilities() { return computedCapabilities; } - public DefaultConfigurationMetadata withAttributes(ImmutableAttributes attributes) { - return new DefaultConfigurationMetadata(getComponentId(), getName(), isTransitive(), isVisible(), getHierarchy(), getArtifacts(), componentMetadataRules, getExcludes(), attributes, lazyConfigDependencies(), dependencyFilter); - } - - public DefaultConfigurationMetadata withAttributes(String newName, ImmutableAttributes attributes) { - return new DefaultConfigurationMetadata(getComponentId(), newName, isTransitive(), isVisible(), getHierarchy(), getArtifacts(), componentMetadataRules, getExcludes(), attributes, lazyConfigDependencies(), dependencyFilter); - } - public DefaultConfigurationMetadata withoutConstraints() { return new DefaultConfigurationMetadata(getComponentId(), getName(), isTransitive(), isVisible(), getHierarchy(), getArtifacts(), componentMetadataRules, getExcludes(), super.getAttributes(), lazyConfigDependencies(), dependencyFilter.dependenciesOnly()); } - public DefaultConfigurationMetadata withConstraintsOnly() { - return new DefaultConfigurationMetadata(getComponentId(), getName(), isTransitive(), isVisible(), getHierarchy(), ImmutableList.of(), componentMetadataRules, getExcludes(), super.getAttributes(), lazyConfigDependencies(), dependencyFilter.constraintsOnly()); - } - private Factory> lazyConfigDependencies() { return new Factory>() { @Nullable @@ -182,14 +171,6 @@ public List create() { }; } - public DefaultConfigurationMetadata withForcedDependencies() { - return new DefaultConfigurationMetadata(getComponentId(), getName(), isTransitive(), isVisible(), getHierarchy(), getArtifacts(), componentMetadataRules, getExcludes(), componentLevelAttributes, lazyConfigDependencies(), dependencyFilter.forcing()); - } - - public DefaultConfigurationMetadata withCapabilities(List capabilities) { - return new DefaultConfigurationMetadata(getComponentId(), getName(), isTransitive(), isVisible(), getHierarchy(), getArtifacts(), componentMetadataRules, getExcludes(), super.getAttributes(), lazyConfigDependencies(), dependencyFilter, capabilities); - } - private ImmutableList force(ImmutableList configDependencies) { ImmutableList.Builder dependencies = new ImmutableList.Builder(); for (ModuleDependencyMetadata configDependency : configDependencies) { @@ -224,6 +205,10 @@ private ImmutableList withConstraints(boolean constrai return filtered == null ? ImmutableList.of() : filtered.build(); } + Builder mutate() { + return new Builder(); + } + private enum DependencyFilter { ALL, CONSTRAINTS_ONLY, @@ -268,4 +253,60 @@ DependencyFilter constraintsOnly() { throw new IllegalStateException("Cannot set constraints only when dependencies only has already been called"); } } + + class Builder { + private String name = DefaultConfigurationMetadata.this.getName(); + private DependencyFilter dependencyFilter = DefaultConfigurationMetadata.this.dependencyFilter; + private List capabilities; + private ImmutableAttributes attributes; + private ImmutableList artifacts; + + Builder withName(String name) { + this.name = name; + return this; + } + + Builder withAttributes(ImmutableAttributes attributes) { + this.attributes = attributes; + return this; + } + + Builder withoutConstraints() { + dependencyFilter = dependencyFilter.dependenciesOnly(); + return this; + } + + Builder withForcedDependencies() { + dependencyFilter = dependencyFilter.forcing(); + return this; + } + + Builder withConstraintsOnly() { + dependencyFilter = dependencyFilter.constraintsOnly(); + artifacts = ImmutableList.of(); + return this; + } + + Builder withCapabilities(List capabilities) { + this.capabilities = capabilities; + return this; + } + + DefaultConfigurationMetadata build() { + return new DefaultConfigurationMetadata( + getComponentId(), + name, + isTransitive(), + isVisible(), + getHierarchy(), + artifacts == null ? DefaultConfigurationMetadata.this.getArtifacts() : artifacts, + componentMetadataRules, + getExcludes(), + attributes == null ? DefaultConfigurationMetadata.super.getAttributes() : attributes, + lazyConfigDependencies(), + dependencyFilter, + capabilities == null ? Cast.uncheckedCast(DefaultConfigurationMetadata.this.getCapabilities().getCapabilities()) : capabilities + ); + } + } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/GradleDependencyMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/GradleDependencyMetadata.java index 21a780022110a..7cb4eeb2edb28 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/GradleDependencyMetadata.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/GradleDependencyMetadata.java @@ -96,7 +96,7 @@ public List getExcludes() { */ @Override public List selectConfigurations(ImmutableAttributes consumerAttributes, ComponentResolveMetadata targetComponent, AttributesSchemaInternal consumerSchema, Collection explicitRequestedCapabilities) { - return ImmutableList.of(AttributeConfigurationSelector.selectConfigurationUsingAttributeMatching(consumerAttributes, explicitRequestedCapabilities, targetComponent, consumerSchema)); + return ImmutableList.of(AttributeConfigurationSelector.selectConfigurationUsingAttributeMatching(consumerAttributes, explicitRequestedCapabilities, targetComponent, consumerSchema, getArtifacts())); } @Override diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/JavaEcosystemVariantDerivationStrategy.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/JavaEcosystemVariantDerivationStrategy.java index f7e392a64d09b..68441f81f42c8 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/JavaEcosystemVariantDerivationStrategy.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/JavaEcosystemVariantDerivationStrategy.java @@ -18,12 +18,14 @@ import com.google.common.collect.ImmutableList; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.attributes.Usage; +import org.gradle.api.capabilities.Capability; import org.gradle.api.internal.artifacts.repositories.metadata.MavenImmutableAttributesFactory; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.internal.component.external.model.maven.DefaultMavenModuleResolveMetadata; import org.gradle.internal.component.model.ConfigurationMetadata; import java.util.Collections; +import java.util.List; public class JavaEcosystemVariantDerivationStrategy implements VariantDerivationStrategy { @Override @@ -39,6 +41,8 @@ public ImmutableList derive(ModuleComponentReso MavenImmutableAttributesFactory attributesFactory = (MavenImmutableAttributesFactory) md.getAttributesFactory(); DefaultConfigurationMetadata compileConfiguration = (DefaultConfigurationMetadata) md.getConfiguration("compile"); DefaultConfigurationMetadata runtimeConfiguration = (DefaultConfigurationMetadata) md.getConfiguration("runtime"); + ModuleComponentIdentifier componentId = md.getId(); + List shadowedPlatformCapability = buildShadowPlatformCapability(componentId); return ImmutableList.of( // When deriving variants for the Java ecosystem, we actually have 2 components "mixed together": the library and the platform // and there's no way to figure out what was the intent when it was published. So we derive variants, but we also need @@ -48,36 +52,44 @@ public ImmutableList derive(ModuleComponentReso // component we cannot mix precise usages with more generic ones) libraryWithUsageAttribute(compileConfiguration, attributes, attributesFactory, Usage.JAVA_API), libraryWithUsageAttribute(runtimeConfiguration, attributes, attributesFactory, Usage.JAVA_RUNTIME), - platformWithUsageAttribute(compileConfiguration, attributes, attributesFactory, Usage.JAVA_API, false), - platformWithUsageAttribute(runtimeConfiguration, attributes, attributesFactory, Usage.JAVA_RUNTIME, false), - platformWithUsageAttribute(compileConfiguration, attributes, attributesFactory, Usage.JAVA_API, true), - platformWithUsageAttribute(runtimeConfiguration, attributes, attributesFactory, Usage.JAVA_RUNTIME, true)); + platformWithUsageAttribute(compileConfiguration, attributes, attributesFactory, Usage.JAVA_API, false, shadowedPlatformCapability), + platformWithUsageAttribute(runtimeConfiguration, attributes, attributesFactory, Usage.JAVA_RUNTIME, false, shadowedPlatformCapability), + platformWithUsageAttribute(compileConfiguration, attributes, attributesFactory, Usage.JAVA_API, true, shadowedPlatformCapability), + platformWithUsageAttribute(runtimeConfiguration, attributes, attributesFactory, Usage.JAVA_RUNTIME, true, shadowedPlatformCapability)); } return null; } + private List buildShadowPlatformCapability(ModuleComponentIdentifier componentId) { + return Collections.singletonList( + new DefaultShadowedCapability(new ImmutableCapability( + componentId.getGroup(), + componentId.getModule(), + componentId.getVersion() + ), "-derived-platform") + ); + } + private static ConfigurationMetadata libraryWithUsageAttribute(DefaultConfigurationMetadata conf, ImmutableAttributes originAttributes, MavenImmutableAttributesFactory attributesFactory, String usage) { ImmutableAttributes attributes = attributesFactory.libraryWithUsage(originAttributes, usage); - return conf.withAttributes(attributes).withoutConstraints(); + return conf.mutate() + .withAttributes(attributes) + .withoutConstraints() + .build(); } - private static ConfigurationMetadata platformWithUsageAttribute(DefaultConfigurationMetadata conf, ImmutableAttributes originAttributes, MavenImmutableAttributesFactory attributesFactory, String usage, boolean enforcedPlatform) { + private static ConfigurationMetadata platformWithUsageAttribute(DefaultConfigurationMetadata conf, ImmutableAttributes originAttributes, MavenImmutableAttributesFactory attributesFactory, String usage, boolean enforcedPlatform, List shadowedPlatformCapability) { ImmutableAttributes attributes = attributesFactory.platformWithUsage(originAttributes, usage, enforcedPlatform); - ModuleComponentIdentifier componentId = conf.getComponentId(); String prefix = enforcedPlatform ? "enforced-platform-" : "platform-"; - DefaultConfigurationMetadata metadata = conf.withAttributes(prefix + conf.getName(), attributes); - ImmutableCapability shadowed = new ImmutableCapability( - componentId.getGroup(), - componentId.getModule(), - componentId.getVersion() - ); - metadata = metadata + DefaultConfigurationMetadata.Builder builder = conf.mutate() + .withName(prefix + conf.getName()) + .withAttributes(attributes) .withConstraintsOnly() - .withCapabilities(Collections.singletonList(new DefaultShadowedCapability(shadowed, "-derived-platform"))); + .withCapabilities(shadowedPlatformCapability); if (enforcedPlatform) { - metadata = metadata.withForcedDependencies(); + builder = builder.withForcedDependencies(); } - return metadata; + return builder.build(); } } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/RealisedConfigurationMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/RealisedConfigurationMetadata.java index 7e6d1229bdfbd..2807f7ffeb7ca 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/RealisedConfigurationMetadata.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/external/model/RealisedConfigurationMetadata.java @@ -20,7 +20,6 @@ import com.google.common.collect.ImmutableSet; import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.internal.attributes.ImmutableAttributes; -import org.gradle.internal.component.model.ConfigurationMetadata; import org.gradle.internal.component.model.DependencyMetadata; import org.gradle.internal.component.model.ExcludeMetadata; @@ -54,13 +53,6 @@ public List getDependencies() { return getConfigDependencies(); } - @Override - public ConfigurationMetadata withAttributes(ImmutableAttributes attributes) { - return new RealisedConfigurationMetadata(getComponentId(), getName(), isTransitive(), isVisible(), - getHierarchy(), ImmutableList.copyOf(getArtifacts()), getExcludes(), attributes, - ImmutableCapabilities.of(getCapabilities().getCapabilities()), getConfigDependencies()); - } - public RealisedConfigurationMetadata withDependencies(ImmutableList dependencies) { return new RealisedConfigurationMetadata( getComponentId(), diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalComponentMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalComponentMetadata.java index 78d75af9559fe..8dfb1433afa4f 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalComponentMetadata.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/local/model/DefaultLocalComponentMetadata.java @@ -30,10 +30,10 @@ import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.artifacts.component.ComponentIdentifier; import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.attributes.Category; import org.gradle.api.capabilities.CapabilitiesMetadata; import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal; import org.gradle.api.internal.artifacts.configurations.OutgoingVariant; -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport; import org.gradle.api.internal.artifacts.ivyservice.moduleconverter.dependencies.LocalConfigurationMetadataBuilder; import org.gradle.api.internal.attributes.AttributeValue; import org.gradle.api.internal.attributes.AttributesSchemaInternal; @@ -407,8 +407,8 @@ public List getDependencies() { } } maybeAddGeneratedDependencies(result); - AttributeValue attributeValue = this.getAttributes().findEntry(PlatformSupport.COMPONENT_CATEGORY); - if (attributeValue.isPresent() && attributeValue.get().equals(PlatformSupport.ENFORCED_PLATFORM)) { + AttributeValue attributeValue = this.getAttributes().findEntry(Category.CATEGORY_ATTRIBUTE); + if (attributeValue.isPresent() && attributeValue.get().getName().equals(Category.ENFORCED_PLATFORM)) { // need to wrap all dependencies to force them ImmutableList rawDependencies = result.build(); result = ImmutableList.builder(); diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/AttributeConfigurationSelector.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/AttributeConfigurationSelector.java index 770f038bac881..8534707a6513e 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/AttributeConfigurationSelector.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/AttributeConfigurationSelector.java @@ -18,6 +18,8 @@ import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import org.gradle.api.artifacts.ArtifactIdentifier; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.capabilities.Capability; @@ -26,14 +28,16 @@ import org.gradle.internal.component.AmbiguousConfigurationSelectionException; import org.gradle.internal.component.NoMatchingCapabilitiesException; import org.gradle.internal.component.NoMatchingConfigurationSelectionException; +import org.gradle.internal.component.external.model.ModuleComponentArtifactMetadata; import org.gradle.internal.component.external.model.ShadowedCapability; import java.util.Collection; +import java.util.Collections; import java.util.List; public abstract class AttributeConfigurationSelector { - public static ConfigurationMetadata selectConfigurationUsingAttributeMatching(ImmutableAttributes consumerAttributes, Collection explicitRequestedCapabilities, ComponentResolveMetadata targetComponent, AttributesSchemaInternal consumerSchema) { + public static ConfigurationMetadata selectConfigurationUsingAttributeMatching(ImmutableAttributes consumerAttributes, Collection explicitRequestedCapabilities, ComponentResolveMetadata targetComponent, AttributesSchemaInternal consumerSchema, List requestedArtifacts) { Optional> variantsForGraphTraversal = targetComponent.getVariantsForGraphTraversal(); ImmutableList consumableConfigurations = variantsForGraphTraversal.or(ImmutableList.of()); AttributesSchemaInternal producerAttributeSchema = targetComponent.getAttributesSchema(); @@ -57,7 +61,7 @@ public static ConfigurationMetadata selectConfigurationUsingAttributeMatching(Im List strictlyMatchingCapabilities = filterVariantsByRequestedCapabilities(targetComponent, explicitRequestedCapabilities, matches, versionId.getGroup(), versionId.getName(), false); if (strictlyMatchingCapabilities.size() == 1) { return singleVariant(variantsForGraphTraversal, matches); - } else if (strictlyMatchingCapabilities.size() > 1){ + } else if (strictlyMatchingCapabilities.size() > 1) { // there are still more than one candidate, but this time we know only a subset strictly matches the required attributes // so we perform another round of selection on the remaining candidates strictlyMatchingCapabilities = attributeMatcher.matches(strictlyMatchingCapabilities, consumerAttributes, fallbackConfiguration); @@ -65,6 +69,18 @@ public static ConfigurationMetadata selectConfigurationUsingAttributeMatching(Im return singleVariant(variantsForGraphTraversal, matches); } } + if (requestedArtifacts.size() == 1) { + // Here, we know that the user requested a specific classifier. There may be multiple + // candidate variants left, but maybe only one of them provides the classified artifact + // we're looking for. + String classifier = requestedArtifacts.get(0).getClassifier(); + if (classifier != null) { + List sameClassifier = findVariantsProvidingExactlySameClassifier(matches, classifier); + if (sameClassifier != null && sameClassifier.size() == 1) { + return singleVariant(variantsForGraphTraversal, sameClassifier); + } + } + } } if (matches.size() == 1) { return singleVariant(variantsForGraphTraversal, matches); @@ -75,6 +91,29 @@ public static ConfigurationMetadata selectConfigurationUsingAttributeMatching(Im } } + private static List findVariantsProvidingExactlySameClassifier(List matches, String classifier) { + List sameClassifier = null; + // let's see if we can find a single variant which has exactly the requested artifacts + for (ConfigurationMetadata match : matches) { + List artifacts = match.getArtifacts(); + if (artifacts.size() == 1) { + ComponentArtifactMetadata componentArtifactMetadata = artifacts.get(0); + if (componentArtifactMetadata instanceof ModuleComponentArtifactMetadata) { + ArtifactIdentifier artifactIdentifier = ((ModuleComponentArtifactMetadata) componentArtifactMetadata).toArtifactIdentifier(); + if (classifier.equals(artifactIdentifier.getClassifier())) { + if (sameClassifier == null) { + sameClassifier = Collections.singletonList(match); + } else { + sameClassifier = Lists.newArrayList(sameClassifier); + sameClassifier.add(match); + } + } + } + } + } + return sameClassifier; + } + private static ConfigurationMetadata singleVariant(Optional> variantsForGraphTraversal, List matches) { ConfigurationMetadata match = matches.get(0); if (variantsForGraphTraversal.isPresent()) { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/LocalComponentDependencyMetadata.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/LocalComponentDependencyMetadata.java index 89e6ca271d1b2..4f60af19f9704 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/LocalComponentDependencyMetadata.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/component/model/LocalComponentDependencyMetadata.java @@ -132,7 +132,7 @@ public List selectConfigurations(ImmutableAttributes cons Optional> targetVariants = targetComponent.getVariantsForGraphTraversal(); boolean useConfigurationAttributes = dependencyConfiguration == null && (consumerHasAttributes || targetVariants.isPresent()); if (useConfigurationAttributes) { - return ImmutableList.of(AttributeConfigurationSelector.selectConfigurationUsingAttributeMatching(consumerAttributes, explicitRequestedCapabilities, targetComponent, consumerSchema)); + return ImmutableList.of(AttributeConfigurationSelector.selectConfigurationUsingAttributeMatching(consumerAttributes, explicitRequestedCapabilities, targetComponent, consumerSchema, getArtifacts())); } String targetConfiguration = GUtil.elvis(dependencyConfiguration, Dependency.DEFAULT_CONFIGURATION); diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/ModuleVersionNotFoundException.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/ModuleVersionNotFoundException.java index 6b000093cbd55..e09ebb466eae6 100755 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/ModuleVersionNotFoundException.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/ModuleVersionNotFoundException.java @@ -19,6 +19,7 @@ import org.gradle.api.artifacts.ModuleVersionIdentifier; import org.gradle.api.artifacts.component.ComponentSelector; import org.gradle.api.artifacts.component.ModuleComponentSelector; +import org.gradle.internal.Factory; import org.gradle.internal.logging.text.TreeFormatter; import java.util.Collection; @@ -29,7 +30,7 @@ public class ModuleVersionNotFoundException extends ModuleVersionResolveExceptio * This is used by {@link ModuleVersionResolveException#withIncomingPaths(java.util.Collection)}. */ @SuppressWarnings("UnusedDeclaration") - public ModuleVersionNotFoundException(ComponentSelector selector, String message) { + public ModuleVersionNotFoundException(ComponentSelector selector, Factory message) { super(selector, message); } @@ -45,32 +46,34 @@ public ModuleVersionNotFoundException(ModuleComponentSelector selector, Collecti super(selector, format(selector, attemptedLocations)); } - private static String format(ModuleComponentSelector selector, Collection locations, Collection unmatchedVersions, Collection rejectedVersions) { - TreeFormatter builder = new TreeFormatter(); - if (unmatchedVersions.isEmpty() && rejectedVersions.isEmpty()) { - builder.node(String.format("Could not find any matches for %s as no versions of %s:%s are available.", selector, selector.getGroup(), selector.getModule())); - } else { - builder.node(String.format("Could not find any version that matches %s.", selector)); - if (!unmatchedVersions.isEmpty()) { - builder.node("Versions that do not match"); - appendSizeLimited(builder, unmatchedVersions); - } - if (!rejectedVersions.isEmpty()) { - Collection byRule = Lists.newArrayListWithExpectedSize(rejectedVersions.size()); - Collection byAttributes = Lists.newArrayListWithExpectedSize(rejectedVersions.size()); - mapRejections(rejectedVersions, byRule, byAttributes); - if (!byRule.isEmpty()) { - builder.node("Versions rejected by component selection rules"); - appendSizeLimited(builder, byRule); + private static Factory format(ModuleComponentSelector selector, Collection locations, Collection unmatchedVersions, Collection rejectedVersions) { + return () -> { + TreeFormatter builder = new TreeFormatter(); + if (unmatchedVersions.isEmpty() && rejectedVersions.isEmpty()) { + builder.node(String.format("Could not find any matches for %s as no versions of %s:%s are available.", selector, selector.getGroup(), selector.getModule())); + } else { + builder.node(String.format("Could not find any version that matches %s.", selector)); + if (!unmatchedVersions.isEmpty()) { + builder.node("Versions that do not match"); + appendSizeLimited(builder, unmatchedVersions); } - if (!byAttributes.isEmpty()) { - builder.node("Versions rejected by attribute matching"); - appendSizeLimited(builder, byAttributes); + if (!rejectedVersions.isEmpty()) { + Collection byRule = Lists.newArrayListWithExpectedSize(rejectedVersions.size()); + Collection byAttributes = Lists.newArrayListWithExpectedSize(rejectedVersions.size()); + mapRejections(rejectedVersions, byRule, byAttributes); + if (!byRule.isEmpty()) { + builder.node("Versions rejected by component selection rules"); + appendSizeLimited(builder, byRule); + } + if (!byAttributes.isEmpty()) { + builder.node("Versions rejected by attribute matching"); + appendSizeLimited(builder, byAttributes); + } } } - } - addLocations(builder, locations); - return builder.toString(); + addLocations(builder, locations); + return builder.toString(); + }; } private static void mapRejections(Collection in, Collection outByRule, Collection outByAttributes) { @@ -83,18 +86,22 @@ private static void mapRejections(Collection in, Collection locations) { - TreeFormatter builder = new TreeFormatter(); - builder.node(String.format("Could not find %s.", id)); - addLocations(builder, locations); - return builder.toString(); + private static Factory format(ModuleVersionIdentifier id, Collection locations) { + return () -> { + TreeFormatter builder = new TreeFormatter(); + builder.node(String.format("Could not find %s.", id)); + addLocations(builder, locations); + return builder.toString(); + }; } - private static String format(ModuleComponentSelector selector, Collection locations) { - TreeFormatter builder = new TreeFormatter(); - builder.node(String.format("Could not find any version that matches %s.", selector)); - addLocations(builder, locations); - return builder.toString(); + private static Factory format(ModuleComponentSelector selector, Collection locations) { + return () -> { + TreeFormatter builder = new TreeFormatter(); + builder.node(String.format("Could not find any version that matches %s.", selector)); + addLocations(builder, locations); + return builder.toString(); + }; } private static void appendSizeLimited(TreeFormatter builder, Collection values) { diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/ModuleVersionResolveException.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/ModuleVersionResolveException.java index 73182e5c4c0a2..4836904f341d5 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/ModuleVersionResolveException.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/ModuleVersionResolveException.java @@ -22,10 +22,11 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier; import org.gradle.api.internal.artifacts.DefaultModuleIdentifier; import org.gradle.api.internal.artifacts.dependencies.DefaultImmutableVersionConstraint; +import org.gradle.internal.Factory; import org.gradle.internal.UncheckedException; import org.gradle.internal.component.external.model.DefaultModuleComponentSelector; import org.gradle.internal.exceptions.Contextual; -import org.gradle.internal.exceptions.DefaultMultiCauseException; +import org.gradle.internal.exceptions.DefaultMultiCauseExceptionNoStackTrace; import java.util.ArrayList; import java.util.Arrays; @@ -34,16 +35,16 @@ import java.util.List; @Contextual -public class ModuleVersionResolveException extends DefaultMultiCauseException { +public class ModuleVersionResolveException extends DefaultMultiCauseExceptionNoStackTrace { private final List> paths = new ArrayList>(); private final ComponentSelector selector; - public ModuleVersionResolveException(ComponentSelector selector, String message, Throwable cause) { + public ModuleVersionResolveException(ComponentSelector selector, Factory message, Throwable cause) { super(message, cause); this.selector = selector; } - public ModuleVersionResolveException(ComponentSelector selector, String message) { + public ModuleVersionResolveException(ComponentSelector selector, Factory message) { super(message); this.selector = selector; } @@ -58,15 +59,15 @@ public ModuleVersionResolveException(ComponentSelector selector, Iterable message) { this(DefaultModuleComponentSelector.newSelector(selector), message); } - public ModuleVersionResolveException(ModuleVersionIdentifier id, String message) { + public ModuleVersionResolveException(ModuleVersionIdentifier id, Factory message) { this(DefaultModuleComponentSelector.newSelector(id.getModule(), DefaultImmutableVersionConstraint.of(id.getVersion())), message); } - public ModuleVersionResolveException(ModuleComponentIdentifier id, String messageFormat) { + public ModuleVersionResolveException(ModuleComponentIdentifier id, Factory messageFormat) { this(DefaultModuleComponentSelector.newSelector(DefaultModuleIdentifier.newId(id.getGroup(), id.getModule()), DefaultImmutableVersionConstraint.of(id.getVersion())), messageFormat); } @@ -93,8 +94,8 @@ public ComponentSelector getSelector() { return selector; } - protected static String format(String messageFormat, ComponentSelector selector) { - return String.format(messageFormat, selector.getDisplayName()); + protected static Factory format(String messageFormat, ComponentSelector selector) { + return () -> String.format(messageFormat, selector.getDisplayName()); } /** @@ -130,10 +131,15 @@ private String toString(ComponentIdentifier identifier) { protected ModuleVersionResolveException createCopy() { try { - return getClass().getConstructor(ComponentSelector.class, String.class).newInstance(selector, getMessage()); + String message = getMessage(); + return getClass().getConstructor(ComponentSelector.class, Factory.class).newInstance(selector, new Factory() { + @Override + public String create() { + return message; + } + }); } catch (Exception e) { throw UncheckedException.throwAsUncheckedException(e); } } - } diff --git a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/caching/DesugaringAttributeContainerSerializer.java b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/caching/DesugaringAttributeContainerSerializer.java index d6c3e959d8465..011ca4b23f940 100644 --- a/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/caching/DesugaringAttributeContainerSerializer.java +++ b/subprojects/dependency-management/src/main/java/org/gradle/internal/resolve/caching/DesugaringAttributeContainerSerializer.java @@ -41,6 +41,7 @@ public class DesugaringAttributeContainerSerializer implements AttributeContaine private static final byte STRING_ATTRIBUTE = 1; private static final byte BOOLEAN_ATTRIBUTE = 2; private static final byte DESUGARED_ATTRIBUTE = 3; + private static final byte INTEGER_ATTRIBUTE = 4; public DesugaringAttributeContainerSerializer(ImmutableAttributesFactory attributesFactory, NamedObjectInstantiator namedObjectInstantiator) { this.attributesFactory = attributesFactory; @@ -59,6 +60,9 @@ public ImmutableAttributes read(Decoder decoder) throws IOException { } else if (type == STRING_ATTRIBUTE){ String value = decoder.readString(); attributes = attributesFactory.concat(attributes, Attribute.of(name, String.class), value); + } else if (type == INTEGER_ATTRIBUTE){ + int value = decoder.readInt(); + attributes = attributesFactory.concat(attributes, Attribute.of(name, Integer.class), value); } else if (type == DESUGARED_ATTRIBUTE) { String value = decoder.readString(); attributes = attributesFactory.concat(attributes, Attribute.of(name, String.class), new CoercingStringValueSnapshot(value, namedObjectInstantiator)); @@ -78,6 +82,9 @@ public void write(Encoder encoder, AttributeContainer container) throws IOExcept } else if (attribute.getType().equals(String.class)){ encoder.writeByte(STRING_ATTRIBUTE); encoder.writeString((String) container.getAttribute(attribute)); + } else if (attribute.getType().equals(Integer.class)){ + encoder.writeByte(INTEGER_ATTRIBUTE); + encoder.writeInt((Integer) container.getAttribute(attribute)); } else { assert Named.class.isAssignableFrom(attribute.getType()); Named attributeValue = (Named) container.getAttribute(attribute); diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyConstraintHandlerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyConstraintHandlerTest.groovy index 41ceccaeac007..f74516f0dc723 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyConstraintHandlerTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyConstraintHandlerTest.groovy @@ -22,7 +22,6 @@ import org.gradle.api.artifacts.ConfigurationContainer import org.gradle.api.artifacts.DependencyConstraint import org.gradle.api.artifacts.DependencyConstraintSet import org.gradle.api.artifacts.VersionConstraint -import org.gradle.api.artifacts.dsl.ComponentMetadataHandler import org.gradle.util.TestUtil import spock.lang.Specification @@ -35,9 +34,8 @@ class DefaultDependencyConstraintHandlerTest extends Specification { private def dependencyFactory = Mock(DependencyFactory) private def configuration = Mock(Configuration) private def dependencyConstraintSet = Mock(DependencyConstraintSet) - private def componentMetadataHandler = Mock(ComponentMetadataHandler) - private DefaultDependencyConstraintHandler dependencyConstraintHandler = TestUtil.instantiatorFactory().decorateLenient().newInstance(DefaultDependencyConstraintHandler, configurationContainer, dependencyFactory, componentMetadataHandler) + private DefaultDependencyConstraintHandler dependencyConstraintHandler = TestUtil.instantiatorFactory().decorateLenient().newInstance(DefaultDependencyConstraintHandler, configurationContainer, dependencyFactory, TestUtil.objectInstantiator()) void setup() { _ * configurationContainer.findByName(TEST_CONF_NAME) >> configuration diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy index 6a3aa350d0660..bffc04d490ae2 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/dsl/dependencies/DefaultDependencyHandlerTest.groovy @@ -48,7 +48,7 @@ class DefaultDependencyHandlerTest extends Specification { private DefaultDependencyHandler dependencyHandler = TestUtil.instantiatorFactory().decorateLenient().newInstance(DefaultDependencyHandler, configurationContainer, dependencyFactory, projectFinder, Stub(DependencyConstraintHandler), Stub(ComponentMetadataHandler), Stub(ComponentModuleMetadataHandler), Stub(ArtifactResolutionQueryFactory), - Stub(AttributesSchema), Stub(VariantTransformRegistry), Stub(Factory)) + Stub(AttributesSchema), Stub(VariantTransformRegistry), Stub(Factory), TestUtil.objectInstantiator()) void setup() { _ * configurationContainer.findByName(TEST_CONF_NAME) >> configuration diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionsSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionsSpec.groovy index e03604a18b283..be7e5d1320c5e 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionsSpec.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/dependencysubstitution/DefaultDependencySubstitutionsSpec.groovy @@ -19,6 +19,7 @@ package org.gradle.api.internal.artifacts.ivyservice.dependencysubstitution import org.gradle.api.Action import org.gradle.api.InvalidUserDataException import org.gradle.api.artifacts.component.ComponentSelector +import org.gradle.api.artifacts.component.ProjectComponentSelector import org.gradle.api.internal.artifacts.ComponentSelectorConverter import org.gradle.api.internal.artifacts.DefaultImmutableModuleIdentifierFactory import org.gradle.api.internal.artifacts.DefaultModuleIdentifier @@ -260,4 +261,43 @@ class DefaultDependencySubstitutionsSpec extends Specification { then: 0 * validator.validateMutation(_) } + + def "registering an all rule toggles the hasRule flag"() { + given: + def action = Mock(Action) + + when: + substitutions.all(action) + + then: + substitutions.hasRules() + } + + @Unroll + def "registering a substitute rule with (#from, #to) causes hasRule #result"() { + given: + componentIdentifierFactory.createProjectComponentSelector(_) >> Mock(ProjectComponentSelector) + def fromComponent = createComponent(from) + def toComponent = createComponent(to) + + when: + substitutions.substitute(fromComponent).with(toComponent) + + then: + substitutions.hasRules() == result + + where: + from | to | result + "org:test" | ":foo" | true + ":bar" | "org:test:1.0" | true + "org:test" | "org:foo:1.0" | false + } + + ComponentSelector createComponent(String componentNotation) { + if (componentNotation.startsWith(":")) { + return substitutions.project(componentNotation) + } else { + return substitutions.module(componentNotation) + } + } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooserTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooserTest.groovy index 425a6062e715a..78f25deef0b67 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooserTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/DefaultVersionedComponentChooserTest.groovy @@ -412,7 +412,7 @@ class DefaultVersionedComponentChooserTest extends Specification { Mock(ComponentMetadataSupplierRuleExecutor) { execute(_, _, _, _, _) >> { args -> def (key, rule, converter, producer, cachePolicy) = args - converter.transform(producer.transform(key, dependenciesProvider), dependenciesProvider) + converter.transform(producer.transform(key, dependenciesProvider, null), dependenciesProvider, null) } } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolverProviderComponentMetaDataResolverTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolverProviderComponentMetaDataResolverTest.groovy index 1442572dade65..8096b23a678be 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolverProviderComponentMetaDataResolverTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/ResolverProviderComponentMetaDataResolverTest.groovy @@ -31,6 +31,7 @@ import org.gradle.internal.resolve.result.BuildableComponentResolveResult import spock.lang.Specification class ResolverProviderComponentMetaDataResolverTest extends Specification { + final org.gradle.internal.Factory broken = { "broken" } final metaData = metaData("1.2") final moduleComponentId = DefaultModuleComponentIdentifier.newId(DefaultModuleIdentifier.newId("group", "project"), "1.0") final componentRequestMetaData = Mock(ComponentOverrideMetadata) @@ -445,7 +446,7 @@ class ResolverProviderComponentMetaDataResolverTest extends Specification { then: 1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result -> - result.failed(new ModuleVersionResolveException(id, "broken")) + result.failed(new ModuleVersionResolveException(id, broken)) } 1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result -> result.resolved(metaData) @@ -478,7 +479,7 @@ class ResolverProviderComponentMetaDataResolverTest extends Specification { then: 1 * localAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) 1 * remoteAccess.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result -> - result.failed(new ModuleVersionResolveException(id, "broken")) + result.failed(new ModuleVersionResolveException(id, broken)) } 1 * localAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) 1 * remoteAccess2.resolveComponentMetaData(moduleComponentId, componentRequestMetaData, _) >> { id, meta, result -> @@ -503,7 +504,7 @@ class ResolverProviderComponentMetaDataResolverTest extends Specification { def "rethrows failure to resolve local dependency when not available in any repository"() { given: - def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), "broken") + def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), broken) def repo1 = addRepo1() def repo2 = addRepo2() @@ -530,7 +531,7 @@ class ResolverProviderComponentMetaDataResolverTest extends Specification { def "rethrows failure to resolve remote dependency when not available in any repository"() { given: - def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), "broken") + def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), broken) def repo1 = addRepo1() def repo2 = addRepo2() diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleMetadataParserTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradleModuleMetadataParserTest.groovy similarity index 89% rename from subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleMetadataParserTest.groovy rename to subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradleModuleMetadataParserTest.groovy index a2616473a076c..16d22b1a15a2e 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/ModuleMetadataParserTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/ivyresolve/parser/GradleModuleMetadataParserTest.groovy @@ -31,15 +31,93 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider import org.gradle.util.AttributeTestUtil import org.junit.Rule import spock.lang.Specification +import spock.lang.Unroll import static org.gradle.util.AttributeTestUtil.attributes -class ModuleMetadataParserTest extends Specification { +class GradleModuleMetadataParserTest extends Specification { + private static final String UNKNOWN_FILE_VALUES = ''' + { + "formatVersion": "1.0", + "variants": [ + { + "name": "api", + "files": [{ + "name": "file", + "url": "file", + "otherString": "string", + "otherNumber": 123, + "otherBoolean": true, + "otherNull": null, + "otherObject": { "a": 1, "b": "ignore-me", "c": [], "d": { } }, + "otherArray": [ "a", 123, false, [], null, { } ] + }] + } + ] + } +''' + private static final String UNKNOWN_DEPENDENCY_VALUES = ''' + { + "formatVersion": "1.0", + "variants": [ + { + "name": "api", + "dependencies": [{ + "group": "g", + "module": "m", + "version": { "prefers": "v" }, + "excludes": [ + { "group": "g", "otherString": "string", "otherNumber": 123, "otherObject": { "a": 1 } } + ], + "otherString": "string", + "otherNumber": 123, + "otherBoolean": true, + "otherNull": null, + "otherObject": { "a": 1, "b": "ignore-me", "c": [], "d": { } }, + "otherArray": [ "a", 123, false, [], null, { } ] + }] + } + ] + } +''' + private static final String UNKNOWN_VARIANT_VALUES = ''' + { + "formatVersion": "1.0", + "variants": [ + { + "name": "api", + "otherString": "string", + "otherNumber": 123, + "otherBoolean": true, + "otherNull": null, + "otherObject": { "a": 1, "b": "ignore-me", "c": [], "d": { } }, + "otherArray": [ "a", 123, false, [], null, { } ] + } + ] + } +''' + private static final String UNKNOWN_TOP_LEVEL = '''{ + "formatVersion": "1.0", + "otherString": "string", + "otherNumber": 123, + "otherBoolean": true, + "otherNull": null, + "otherObject": { "a": 1, "b": "ignore-me", "c": [], "d": { } }, + "otherArray": [ "a", 123, false, [], null, { } ] + }''' + + private static final Map UNKOWN_DATASET = [ + UNKNOWN_TOP_LEVEL: UNKNOWN_TOP_LEVEL, + UNKNOWN_DEPENDENCY_VALUES: UNKNOWN_DEPENDENCY_VALUES, + UNKNOWN_VARIANT_VALUES: UNKNOWN_VARIANT_VALUES, + UNKNOWN_FILE_VALUES: UNKNOWN_FILE_VALUES + ] + @Rule final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider() def identifierFactory = new DefaultImmutableModuleIdentifierFactory() - def parser = new ModuleMetadataParser(AttributeTestUtil.attributesFactory(), identifierFactory, NamedObjectInstantiator.INSTANCE) + def parser = new GradleModuleMetadataParser(AttributeTestUtil.attributesFactory(), identifierFactory, NamedObjectInstantiator.INSTANCE) VersionConstraint emptyConstraint() { DefaultImmutableVersionConstraint.of() @@ -54,7 +132,7 @@ class ModuleMetadataParserTest extends Specification { } VersionConstraint prefers(String version) { - DefaultImmutableVersionConstraint.of(version,'', '', []) + DefaultImmutableVersionConstraint.of(version, '', '', []) } VersionConstraint strictly(String version) { @@ -80,7 +158,7 @@ class ModuleMetadataParserTest extends Specification { def metadata = Mock(MutableModuleComponentResolveMetadata) when: - parser.parse(resource('{ "formatVersion": "0.4" }'), metadata) + parser.parse(resource('{ "formatVersion": "1.0" }'), metadata) then: 1 * metadata.setContentHash(_) @@ -93,7 +171,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "component": { "url": "elsewhere", "group": "g", "module": "m", "version": "v" }, "builtBy": { "gradle": { "version": "123", "buildId": "abc" } } } @@ -110,7 +188,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "component": { "url": "elsewhere", "group": "g", "module": "m", "version": "v", "attributes": {"foo": "bar", "org.gradle.status": "release" } }, "builtBy": { "gradle": { "version": "123", "buildId": "abc" } } } @@ -129,7 +207,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", @@ -157,7 +235,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", @@ -186,7 +264,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", @@ -227,7 +305,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", @@ -268,7 +346,7 @@ class ModuleMetadataParserTest extends Specification { 1 * variant1.addDependency("g2", "m2", prefers("v2"), [], null, ImmutableAttributes.EMPTY, []) 1 * variant1.addDependency("g3", "m3", requires("v3"), excludes("gx:mx", "*:*"), null, ImmutableAttributes.EMPTY, []) 1 * metadata.addVariant("runtime", attributes(usage: "runtime", packaging: "zip")) >> variant2 - 1 * variant2.addDependency("g3", "m3", prefers("v3"), [], null, ImmutableAttributes.EMPTY, { it[0].group == 'org' && it[0].name=='foo' && it[0].version=='1.0'}) + 1 * variant2.addDependency("g3", "m3", prefers("v3"), [], null, ImmutableAttributes.EMPTY, { it[0].group == 'org' && it[0].name == 'foo' && it[0].version == '1.0' }) 1 * variant2.addDependency("g4", "m4", strictly("v5"), [], null, ImmutableAttributes.EMPTY, []) 1 * variant2.addDependency("g5", "m5", prefersAndRejects("v5", ["v6", "v7"]), [], null, ImmutableAttributes.EMPTY, []) 1 * variant2.addDependency("g6", "m6", strictly("v6"), [], "v5 is buggy", ImmutableAttributes.EMPTY, []) @@ -284,7 +362,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", @@ -335,7 +413,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", @@ -376,7 +454,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", @@ -416,7 +494,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "builtBy": { "gradle": { "version": "123", "buildId": "abc" } }, "variants": [ { @@ -441,7 +519,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api" @@ -471,7 +549,7 @@ class ModuleMetadataParserTest extends Specification { when: parser.parse(resource(''' { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", @@ -521,15 +599,7 @@ class ModuleMetadataParserTest extends Specification { def metadata = Mock(MutableModuleComponentResolveMetadata) when: - parser.parse(resource('''{ - "formatVersion": "0.4", - "otherString": "string", - "otherNumber": 123, - "otherBoolean": true, - "otherNull": null, - "otherObject": { "a": 1, "b": "ignore-me", "c": [], "d": { } }, - "otherArray": [ "a", 123, false, [], null, { } ] - }'''), metadata) + parser.parse(resource(UNKNOWN_TOP_LEVEL), metadata) then: 1 * metadata.setContentHash(_) @@ -540,22 +610,7 @@ class ModuleMetadataParserTest extends Specification { def metadata = Mock(MutableModuleComponentResolveMetadata) when: - parser.parse(resource(''' - { - "formatVersion": "0.4", - "variants": [ - { - "name": "api", - "otherString": "string", - "otherNumber": 123, - "otherBoolean": true, - "otherNull": null, - "otherObject": { "a": 1, "b": "ignore-me", "c": [], "d": { } }, - "otherArray": [ "a", 123, false, [], null, { } ] - } - ] - } -'''), metadata) + parser.parse(resource(UNKNOWN_VARIANT_VALUES), metadata) then: 1 * metadata.addVariant("api", attributes([:])) @@ -568,26 +623,7 @@ class ModuleMetadataParserTest extends Specification { def variant = Mock(MutableComponentVariant) when: - parser.parse(resource(''' - { - "formatVersion": "0.4", - "variants": [ - { - "name": "api", - "files": [{ - "name": "file", - "url": "file", - "otherString": "string", - "otherNumber": 123, - "otherBoolean": true, - "otherNull": null, - "otherObject": { "a": 1, "b": "ignore-me", "c": [], "d": { } }, - "otherArray": [ "a", 123, false, [], null, { } ] - }] - } - ] - } -'''), metadata) + parser.parse(resource(UNKNOWN_FILE_VALUES), metadata) then: 1 * metadata.addVariant("api", attributes([:])) >> variant @@ -600,30 +636,7 @@ class ModuleMetadataParserTest extends Specification { def variant = Mock(MutableComponentVariant) when: - parser.parse(resource(''' - { - "formatVersion": "0.4", - "variants": [ - { - "name": "api", - "dependencies": [{ - "group": "g", - "module": "m", - "version": { "prefers": "v" }, - "excludes": [ - { "group": "g", "otherString": "string", "otherNumber": 123, "otherObject": { "a": 1 } } - ], - "otherString": "string", - "otherNumber": 123, - "otherBoolean": true, - "otherNull": null, - "otherObject": { "a": 1, "b": "ignore-me", "c": [], "d": { } }, - "otherArray": [ "a", 123, false, [], null, { } ] - }] - } - ] - } -'''), metadata) + parser.parse(resource(UNKNOWN_DEPENDENCY_VALUES), metadata) then: 1 * metadata.addVariant("api", attributes([:])) >> variant @@ -676,16 +689,37 @@ class ModuleMetadataParserTest extends Specification { e.cause.message == "The 'formatVersion' value should have a string value." } - def "fails on unsupported format version"() { + def "fails on unsupported format version if json parsing fails and metadata format is not the expected one"() { def metadata = Mock(MutableModuleComponentResolveMetadata) when: - parser.parse(resource('{ "formatVersion": "123.4" }'), metadata) + parser.parse(resource('{ "formatVersion": "123.4", "variants": {} }'), metadata) then: def e = thrown(MetaDataParseException) - e.message == "Could not parse module metadata " - e.cause.message == "Unsupported format version '123.4' specified in module metadata. This version of Gradle supports format version 0.4 only." + e.message == "Could not parse module metadata : unsupported format version '123.4' specified in module metadata. This version of Gradle supports format version 1.0." + e.cause.message == "Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 42 path \$.variants" + } + + @Unroll + def "is lenient with version checks if we manage to parse content (#label, version = #version)"() { + def metadata = Mock(MutableModuleComponentResolveMetadata) { + addVariant(_, _) >> Stub(MutableComponentVariant) + } + + when: + parser.parse(resource(replaceMetadataVersion(json, version)), metadata) + + then: + 1 * metadata.setContentHash(_) + + where: + [json, version] << [UNKOWN_DATASET.values(), ['0.4', '1.1', '1.5', '123.4']].combinations() + label = UNKOWN_DATASET.entrySet().find { it.value == json }.key + } + + String replaceMetadataVersion(String json, String metadataVersion) { + json.replace('"formatVersion": "1.0"', '"formatVersion": "' + metadataVersion + '"') } def resource(String content) { diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest.groovy index dcda90a665728..6f5db2e676290 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest.groovy @@ -26,7 +26,7 @@ import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradlePomM import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyModuleDescriptorConverter import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.IvyXmlModuleDescriptorParser import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionComparator import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.DefaultVersionSelectorScheme import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.MavenVersionSelectorScheme @@ -59,7 +59,7 @@ class ModuleMetadataSerializerTest extends Specification { private final ModuleMetadataSerializer serializer = moduleMetadataSerializer() private GradlePomModuleDescriptorParser pomModuleDescriptorParser = pomParser() private MetaDataParser ivyDescriptorParser = ivyParser() - private ModuleMetadataParser gradleMetadataParser = gradleMetadataParser() + private GradleModuleMetadataParser gradleMetadataParser = gradleMetadataParser() def "all samples are different"() { given: @@ -175,8 +175,8 @@ class ModuleMetadataSerializerTest extends Specification { ) } - private ModuleMetadataParser gradleMetadataParser() { - new ModuleMetadataParser( + private GradleModuleMetadataParser gradleMetadataParser() { + new GradleModuleMetadataParser( AttributeTestUtil.attributesFactory(), moduleIdentifierFactory, NamedObjectInstantiator.INSTANCE diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy index 89def6245adb6..e21f954881879 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/DependencyGraphBuilderTest.groovy @@ -1069,7 +1069,8 @@ class DependencyGraphBuilderTest extends Specification { def dependencyMetaData = dependsOn(args, from, to.moduleVersionId) selectorResolvesTo(dependencyMetaData, to.id, to.moduleVersionId) 1 * metaDataResolver.resolve(to.id, _, _) >> { ComponentIdentifier id, ComponentOverrideMetadata requestMetaData, BuildableComponentResolveResult result -> - result.failed(new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), new DefaultMutableVersionConstraint("c")), "broken")) + org.gradle.internal.Factory broken = { "broken" } + result.failed(new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), new DefaultMutableVersionConstraint("c")), broken)) } } @@ -1084,7 +1085,8 @@ class DependencyGraphBuilderTest extends Specification { def brokenSelector(Map args = [:], def from, String to) { def dependencyMetaData = dependsOn(args, from, newId("group", to, "1.0")) 1 * idResolver.resolve(dependencyMetaData, _, _, _) >> { DependencyMetadata dep, VersionSelector acceptor, VersionSelector rejector, BuildableComponentIdResolveResult result -> - result.failed(new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), new DefaultMutableVersionConstraint("c")), "broken")) + org.gradle.internal.Factory broken = { "broken" } + result.failed(new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), new DefaultMutableVersionConstraint("c")), broken)) } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy index ff8ff1afc5a34..a2b8057391209 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/CachingDependencyResultFactoryTest.groovy @@ -74,15 +74,16 @@ class CachingDependencyResultFactoryTest extends Specification { def "creates and caches unresolved dependencies"() { def fromModule = newModule('from') def selectedModule = Mock(ComponentSelectionReason) + org.gradle.internal.Factory broken = { " foo" } when: - def dep = factory.createUnresolvedDependency(selector('requested'), fromModule, false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo")) - def same = factory.createUnresolvedDependency(selector('requested'), fromModule, false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo")) + def dep = factory.createUnresolvedDependency(selector('requested'), fromModule, false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), broken)) + def same = factory.createUnresolvedDependency(selector('requested'), fromModule, false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), broken)) - def differentRequested = factory.createUnresolvedDependency(selector('xxx'), fromModule, false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('xxx'), "foo")) - def differentFrom = factory.createUnresolvedDependency(selector('requested'), newModule('xxx'), false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo")) - def differentConstraint = factory.createUnresolvedDependency(selector('requested'), fromModule, true, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo")) - def differentFailure = factory.createUnresolvedDependency(selector('requested'), fromModule, false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), "foo")) + def differentRequested = factory.createUnresolvedDependency(selector('xxx'), fromModule, false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('xxx'), broken)) + def differentFrom = factory.createUnresolvedDependency(selector('requested'), newModule('xxx'), false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), broken)) + def differentConstraint = factory.createUnresolvedDependency(selector('requested'), fromModule, true, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), broken)) + def differentFailure = factory.createUnresolvedDependency(selector('requested'), fromModule, false, selectedModule, new ModuleVersionResolveException(moduleVersionSelector('requested'), broken)) then: dep.is(same) diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultComponentSelectionReasonTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultComponentSelectionReasonTest.groovy new file mode 100644 index 0000000000000..245ade5b07dc0 --- /dev/null +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/ivyservice/resolveengine/result/DefaultComponentSelectionReasonTest.groovy @@ -0,0 +1,64 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.artifacts.ivyservice.resolveengine.result + +import org.gradle.api.artifacts.result.ComponentSelectionCause +import org.gradle.api.artifacts.result.ComponentSelectionReason +import org.gradle.internal.Describables +import spock.lang.Specification + +class DefaultComponentSelectionReasonTest extends Specification { + + def "requested only selection reason is expected"() { + when: + def reason = ComponentSelectionReasons.requested() + + then: + reason.isExpected() + } + + def "root only selection reason is expected"() { + when: + def reason = ComponentSelectionReasons.root() + + then: + reason.isExpected() + } + + def "requested with other selection reason is not expected"() { + given: + def reason = ComponentSelectionReasons.requested() + + when: + addCause(reason, ComponentSelectionCause.CONFLICT_RESOLUTION, "test") + + then: + !reason.isExpected() + } + + def "other selection reason and requested is not expected"() { + when: + def reason = ComponentSelectionReasons.of(new DefaultComponentSelectionDescriptor(ComponentSelectionCause.REQUESTED), new DefaultComponentSelectionDescriptor(ComponentSelectionCause.FORCED)) + + then: + !reason.isExpected() + } + + def addCause(ComponentSelectionReason reason, ComponentSelectionCause cause, String description) { + ((ComponentSelectionReasonInternal) reason).addCause(cause, Describables.of(description)) + } +} diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy index 4392a42bd1a66..4cffd37084523 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultBaseRepositoryFactoryTest.groovy @@ -22,7 +22,7 @@ import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler import org.gradle.api.internal.artifacts.ivyservice.IvyContextManager import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser import org.gradle.api.internal.artifacts.mvnsettings.LocalMavenRepositoryLocator import org.gradle.api.internal.artifacts.repositories.metadata.IvyMutableModuleMetadataFactory import org.gradle.api.internal.artifacts.repositories.metadata.MavenMutableModuleMetadataFactory @@ -50,7 +50,7 @@ class DefaultBaseRepositoryFactoryTest extends Specification { final ArtifactIdentifierFileStore artifactIdentifierFileStore = Stub() final ExternalResourceFileStore externalResourceFileStore = Stub() final MetaDataParser pomParser = Mock() - final ModuleMetadataParser metadataParser = Mock() + final GradleModuleMetadataParser metadataParser = Mock() final ivyContextManager = Mock(IvyContextManager) final AuthenticationSchemeRegistry authenticationSchemeRegistry = new DefaultAuthenticationSchemeRegistry() final ImmutableModuleIdentifierFactory moduleIdentifierFactory = Mock() diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy index 0780b344f27fd..2429bffa989e7 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultIvyArtifactRepositoryTest.groovy @@ -24,7 +24,7 @@ import org.gradle.api.artifacts.repositories.AuthenticationContainer import org.gradle.api.internal.artifacts.DefaultImmutableModuleIdentifierFactory import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory import org.gradle.api.internal.artifacts.ivyservice.IvyContextManager -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser import org.gradle.api.internal.artifacts.repositories.metadata.IvyMutableModuleMetadataFactory import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport @@ -58,7 +58,7 @@ class DefaultIvyArtifactRepositoryTest extends Specification { final AuthenticationContainer authenticationContainer = Stub() final ivyContextManager = Mock(IvyContextManager) final ImmutableModuleIdentifierFactory moduleIdentifierFactory = Mock() - final ModuleMetadataParser moduleMetadataParser = new ModuleMetadataParser(Mock(ImmutableAttributesFactory), moduleIdentifierFactory, Mock(NamedObjectInstantiator)) + final GradleModuleMetadataParser moduleMetadataParser = new GradleModuleMetadataParser(Mock(ImmutableAttributesFactory), moduleIdentifierFactory, Mock(NamedObjectInstantiator)) final IvyMutableModuleMetadataFactory metadataFactory = new IvyMutableModuleMetadataFactory(new DefaultImmutableModuleIdentifierFactory(), AttributeTestUtil.attributesFactory()) final DefaultIvyArtifactRepository repository = instantiator.newInstance(DefaultIvyArtifactRepository.class, fileResolver, transportFactory, locallyAvailableResourceFinder, artifactIdentifierFileStore, externalResourceFileStore, authenticationContainer, ivyContextManager, moduleIdentifierFactory, TestUtil.instantiatorFactory(), Mock(FileResourceRepository), moduleMetadataParser, TestUtil.featurePreviews(), metadataFactory, SnapshotTestUtil.valueSnapshotter(), Mock(ObjectFactory)) diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy index bfb2c9058b93a..da1d75037ea8a 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenArtifactRepositoryTest.groovy @@ -23,7 +23,7 @@ import org.gradle.api.artifacts.ComponentMetadataVersionLister import org.gradle.api.artifacts.repositories.AuthenticationContainer import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser import org.gradle.api.internal.artifacts.repositories.metadata.MavenMutableModuleMetadataFactory import org.gradle.api.internal.artifacts.repositories.resolver.MavenResolver import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport @@ -50,7 +50,7 @@ class DefaultMavenArtifactRepositoryTest extends Specification { final ArtifactIdentifierFileStore artifactIdentifierFileStore = Stub() final ExternalResourceFileStore externalResourceFileStore = Stub() final MetaDataParser pomParser = Stub() - final ModuleMetadataParser metadataParser = Stub() + final GradleModuleMetadataParser metadataParser = Stub() final AuthenticationContainer authenticationContainer = Stub() final ImmutableModuleIdentifierFactory moduleIdentifierFactory = Stub() final MavenMutableModuleMetadataFactory mavenMetadataFactory = new MavenMutableModuleMetadataFactory(moduleIdentifierFactory, AttributeTestUtil.attributesFactory(), TestUtil.objectInstantiator(), TestUtil.featurePreviews()) diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalRepositoryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalRepositoryTest.groovy index ec6e7efc4a668..b57920c3b0822 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalRepositoryTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/repositories/DefaultMavenLocalRepositoryTest.groovy @@ -18,7 +18,7 @@ package org.gradle.api.internal.artifacts.repositories import org.gradle.api.artifacts.repositories.AuthenticationContainer import org.gradle.api.internal.artifacts.ImmutableModuleIdentifierFactory import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.MetaDataParser -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser import org.gradle.api.internal.artifacts.repositories.metadata.MavenMutableModuleMetadataFactory import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransport import org.gradle.api.internal.artifacts.repositories.transport.RepositoryTransportFactory @@ -41,7 +41,7 @@ class DefaultMavenLocalRepositoryTest extends Specification { final ExternalResourceRepository resourceRepository = Mock() final ArtifactIdentifierFileStore artifactIdentifierFileStore = Stub() final MetaDataParser pomParser = Stub() - final ModuleMetadataParser metadataParser = Stub() + final GradleModuleMetadataParser metadataParser = Stub() final AuthenticationContainer authenticationContainer = Stub() final ImmutableModuleIdentifierFactory moduleIdentifierFactory = Mock() final MavenMutableModuleMetadataFactory mavenMetadataFactory = new MavenMutableModuleMetadataFactory(moduleIdentifierFactory, AttributeTestUtil.attributesFactory(), TestUtil.objectInstantiator(), TestUtil.featurePreviews()) diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy index 5c1a7893f3ae3..a141ee8da1455 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/result/DefaultResolutionResultTest.groovy @@ -132,11 +132,12 @@ class DefaultResolutionResultTest extends Specification { 'test project' ) def mid = DefaultModuleVersionIdentifier.newId("foo", "bar", "1.0") + org.gradle.internal.Factory broken = { "too bad" } def dep = new DefaultUnresolvedDependencyResult( Stub(ComponentSelector), false, Stub(ComponentSelectionReason), new DefaultResolvedComponentResult(mid, Stub(ComponentSelectionReason), projectId, [Stub(ResolvedVariantResult)], null), - new ModuleVersionNotFoundException(Stub(ModuleComponentSelector), "too bad") + new ModuleVersionNotFoundException(Stub(ModuleComponentSelector), broken) ) def edge = new UnresolvedDependencyEdge(dep) diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/AttributeMatchingVariantSelectorSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/AttributeMatchingVariantSelectorSpec.groovy index 02d9b6a5a5485..ae2a0c6293a23 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/AttributeMatchingVariantSelectorSpec.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/AttributeMatchingVariantSelectorSpec.groovy @@ -32,6 +32,7 @@ import spock.lang.Specification class AttributeMatchingVariantSelectorSpec extends Specification { def consumerProvidedVariantFinder = Mock(ConsumerProvidedVariantFinder) + def transformationNodeRegistry = Mock(TransformationNodeRegistry) def attributeMatcher = Mock(AttributeMatcher) def attributesSchema = Mock(AttributesSchemaInternal) { withProducer(_) >> attributeMatcher @@ -55,7 +56,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { def 'direct match on variant means no finder interaction'() { given: def resolvedArtifactSet = Mock(ResolvedArtifactSet) - def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, requestedAttributes, false, dependenciesResolverFactory) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) when: def result = selector.select(variantSet) @@ -73,7 +74,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { asDescribable() >> Describables.of('other mocked variant') getAttributes() >> otherVariantAttributes } - def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, requestedAttributes, false, dependenciesResolverFactory) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) when: def result = selector.select(variantSet) @@ -106,7 +107,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { def 'selecting a transform results in added DefaultTransformationDependency'() { given: - def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, requestedAttributes, false, dependenciesResolverFactory) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) when: def result = selector.select(variantSet) @@ -152,7 +153,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { } def transform1 = Mock(Transformation) def transform2 = Mock(Transformation) - def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, requestedAttributes, false, dependenciesResolverFactory) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) when: def result = selector.select(multiVariantSet) @@ -189,6 +190,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { 1 * consumerProvidedVariantFinder.collectConsumerVariants(otherVariantAttributes, requestedAttributes, _) >> { args -> args[2].matched(requestedAttributes, transform2, 3) } + 1 * attributeMatcher.matches(_, _) >> { args -> args[0] } } def 'can disambiguate 2 equivalent chains by picking shortest'() { @@ -203,7 +205,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { } def transform1 = Mock(Transformation) def transform2 = Mock(Transformation) - def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, requestedAttributes, false, dependenciesResolverFactory) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) when: def result = selector.select(multiVariantSet) @@ -240,6 +242,58 @@ class AttributeMatchingVariantSelectorSpec extends Specification { 1 * consumerProvidedVariantFinder.collectConsumerVariants(otherVariantAttributes, requestedAttributes, _) >> { args -> args[2].matched(requestedAttributes, transform2, 3) } + 1 * attributeMatcher.matches(_, _) >> { args -> args[0] } + } + + def 'can leverage schema disambiguation'() { + given: + def otherVariant = Mock(ResolvedVariant) { + getAttributes() >> otherVariantAttributes + asDescribable() >> Describables.of("mock other resolved variant") + } + def multiVariantSet = Mock(ResolvedVariantSet) { + asDescribable() >> Describables.of("mock multi producer") + getVariants() >> [variant, otherVariant] + } + def transform1 = Mock(Transformation) + def transform2 = Mock(Transformation) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) + + when: + def result = selector.select(multiVariantSet) + + + then: + result.visitDependencies(new TaskDependencyResolveContext() { + @Override + void add(Object dependency) { + assert dependency instanceof DefaultTransformationDependency + assert dependency.transformation == transform1 + } + + @Override + void maybeAdd(Object dependency) { + + } + + @Override + void visitFailure(Throwable failure) { + throw failure + } + + @Override + Task getTask() { + return null + } + }) + 1 * attributeMatcher.matches(_, _) >> Collections.emptyList() + 1 * consumerProvidedVariantFinder.collectConsumerVariants(variantAttributes, requestedAttributes, _) >> { args -> + args[2].matched(otherVariantAttributes, transform1, 2) + } + 1 * consumerProvidedVariantFinder.collectConsumerVariants(otherVariantAttributes, requestedAttributes, _) >> { args -> + args[2].matched(variantAttributes, transform2, 3) + } + 1 * attributeMatcher.matches(_, _) >> { args -> [args[0].get(0)] } } def 'can disambiguate between three chains when one subset of both others'() { @@ -260,7 +314,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { def transform1 = Mock(Transformation) def transform2 = Mock(Transformation) def transform3 = Mock(Transformation) - def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, requestedAttributes, false, dependenciesResolverFactory) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) when: def result = selector.select(multiVariantSet) @@ -300,6 +354,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { 1 * consumerProvidedVariantFinder.collectConsumerVariants(yetAnotherVariantAttributes, requestedAttributes, _) >> { args -> args[2].matched(requestedAttributes, transform3, 2) } + 1 * attributeMatcher.matches(_, _) >> { args -> args[0] } } def 'can disambiguate 3 equivalent chains by picking shortest'() { @@ -320,7 +375,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { def transform1 = Mock(Transformation) def transform2 = Mock(Transformation) def transform3 = Mock(Transformation) - def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, requestedAttributes, false, dependenciesResolverFactory) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) when: def result = selector.select(multiVariantSet) @@ -350,7 +405,6 @@ class AttributeMatchingVariantSelectorSpec extends Specification { } }) 1 * attributeMatcher.matches(_, _) >> Collections.emptyList() - 2 * attributeMatcher.isMatching(requestedAttributes, requestedAttributes) >> true 1 * consumerProvidedVariantFinder.collectConsumerVariants(variantAttributes, requestedAttributes, _) >> { args -> args[2].matched(requestedAttributes, transform1, 3) } @@ -360,6 +414,8 @@ class AttributeMatchingVariantSelectorSpec extends Specification { 1 * consumerProvidedVariantFinder.collectConsumerVariants(yetAnotherVariantAttributes, requestedAttributes, _) >> { args -> args[2].matched(requestedAttributes, transform3, 3) } + 2 * attributeMatcher.isMatching(requestedAttributes, requestedAttributes) >> true + 1 * attributeMatcher.matches(_, _) >> { args -> args[0] } } def 'cannot disambiguate 3 chains when 2 different'() { @@ -380,7 +436,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { def transform1 = Mock(Transformation) def transform2 = Mock(Transformation) def transform3 = Mock(Transformation) - def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, requestedAttributes, false, dependenciesResolverFactory) + def selector = new AttributeMatchingVariantSelector(consumerProvidedVariantFinder, attributesSchema, attributesFactory, transformationNodeRegistry, requestedAttributes, false, dependenciesResolverFactory) when: def result = selector.select(multiVariantSet) @@ -409,7 +465,6 @@ class AttributeMatchingVariantSelectorSpec extends Specification { } }) 1 * attributeMatcher.matches(_, _) >> Collections.emptyList() - 3 * attributeMatcher.isMatching(requestedAttributes, requestedAttributes) >>> [false, false, true] 1 * consumerProvidedVariantFinder.collectConsumerVariants(variantAttributes, requestedAttributes, _) >> { args -> args[2].matched(requestedAttributes, transform1, 3) } @@ -419,5 +474,7 @@ class AttributeMatchingVariantSelectorSpec extends Specification { 1 * consumerProvidedVariantFinder.collectConsumerVariants(yetAnotherVariantAttributes, requestedAttributes, _) >> { args -> args[2].matched(requestedAttributes, transform3, 3) } + 1 * attributeMatcher.matches(_, _) >> { args -> args[0] } + 3 * attributeMatcher.isMatching(requestedAttributes, requestedAttributes) >>> [false, false, true] } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/ChainedTransformerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/ChainedTransformerTest.groovy index f271a978be379..c98cd9758c423 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/ChainedTransformerTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/ChainedTransformerTest.groovy @@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.transform import com.google.common.collect.ImmutableList import org.gradle.api.Action +import org.gradle.execution.ProjectExecutionServiceRegistry import org.gradle.internal.Try import spock.lang.Specification @@ -29,13 +30,13 @@ class ChainedTransformerTest extends Specification { def chain = new TransformationChain(new CachingTransformation(), new NonCachingTransformation()) expect: - chain.transform(initialSubject, Mock(ExecutionGraphDependenciesResolver)).get().files == [new File("foo/cached/non-cached")] + chain.transform(initialSubject, Mock(ExecutionGraphDependenciesResolver), null).get().files == [new File("foo/cached/non-cached")] } class CachingTransformation implements Transformation { @Override - Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver) { + Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver, ProjectExecutionServiceRegistry services) { return Try.successful(subjectToTransform.createSubjectFromResult(ImmutableList.of(new File(subjectToTransform.files.first(), "cached")))) } @@ -68,7 +69,7 @@ class ChainedTransformerTest extends Specification { class NonCachingTransformation implements Transformation { @Override - Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver) { + Try transform(TransformationSubject subjectToTransform, ExecutionGraphDependenciesResolver dependenciesResolver, ProjectExecutionServiceRegistry services) { return Try.successful(subjectToTransform.createSubjectFromResult(ImmutableList.of(new File(subjectToTransform.files.first(), "non-cached")))) } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/ConsumerProvidedVariantFinderTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/ConsumerProvidedVariantFinderTest.groovy index 513f1a32f6fb2..af1c8f995e18c 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/ConsumerProvidedVariantFinderTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/ConsumerProvidedVariantFinderTest.groovy @@ -203,10 +203,11 @@ class ConsumerProvidedVariantFinderTest extends Specification { 0 * matcher._ when: - def result = transformer.transformation.transform(initialSubject("in.txt"), Mock(ExecutionGraphDependenciesResolver)).get() + def result = transformer.transformation.transform(initialSubject("in.txt"), Mock(ExecutionGraphDependenciesResolver), null).get() then: result.files == [new File("in.txt.2a.5"), new File("in.txt.2b.5")] + 0 * _ } def "prefers direct transformation over indirect"() { @@ -287,7 +288,7 @@ class ConsumerProvidedVariantFinderTest extends Specification { 0 * matcher._ when: - def files = result.matches.first().transformation.transform(initialSubject("a"), Mock(ExecutionGraphDependenciesResolver)).get().files + def files = result.matches.first().transformation.transform(initialSubject("a"), Mock(ExecutionGraphDependenciesResolver), null).get().files then: files == [new File("d"), new File("e")] @@ -443,7 +444,7 @@ class ConsumerProvidedVariantFinderTest extends Specification { reg.from >> from reg.to >> to reg.transformationStep >> Stub(TransformationStep) { - transform(_ as TransformationSubject, _ as ExecutionGraphDependenciesResolver) >> { TransformationSubject subject, ExecutionGraphDependenciesResolver dependenciesResolver -> + _ * transform(_ as TransformationSubject, _ as ExecutionGraphDependenciesResolver, null) >> { TransformationSubject subject, ExecutionGraphDependenciesResolver dependenciesResolver, services -> return Try.successful(subject.createSubjectFromResult(ImmutableList.copyOf(subject.files.collectMany { transformer.transform(it) }))) } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultArtifactTransformsTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultArtifactTransformsTest.groovy index fbdb00daa9fa0..93efcd140c144 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultArtifactTransformsTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultArtifactTransformsTest.groovy @@ -50,7 +50,8 @@ class DefaultArtifactTransformsTest extends Specification { def consumerSchema = Mock(AttributesSchemaInternal) def attributeMatcher = Mock(AttributeMatcher) def dependenciesResolver = Stub(ExtraExecutionGraphDependenciesResolverFactory) - def transforms = new DefaultArtifactTransforms(matchingCache, consumerSchema, AttributeTestUtil.attributesFactory()) + def transformationNodeRegistry = Mock(TransformationNodeRegistry) + def transforms = new DefaultArtifactTransforms(matchingCache, consumerSchema, AttributeTestUtil.attributesFactory(), transformationNodeRegistry) def "selects producer variant with requested attributes"() { def variant1 = resolvedVariant() @@ -171,10 +172,11 @@ class DefaultArtifactTransformsTest extends Specification { } _ * transformation.getDisplayName() >> "transform" _ * transformation.requiresDependencies() >> false + _ * transformationNodeRegistry.getCompleted(_, _) >> Optional.empty() - 1 * transformation.transform({ it.files == [sourceArtifactFile]}, _ as ExecutionGraphDependenciesResolver) >> Try.successful(TransformationSubject.initial(sourceArtifactId, sourceArtifactFile).createSubjectFromResult(ImmutableList.of(outFile1, outFile2))) + 1 * transformation.transform({ it.files == [sourceArtifactFile]}, _ as ExecutionGraphDependenciesResolver, _) >> Try.successful(TransformationSubject.initial(sourceArtifactId, sourceArtifactFile).createSubjectFromResult(ImmutableList.of(outFile1, outFile2))) - 1 * transformation.transform({ it.files == [sourceFile] }, _ as ExecutionGraphDependenciesResolver) >> Try.successful(TransformationSubject.initial(sourceFile).createSubjectFromResult(ImmutableList.of(outFile3, outFile4))) + 1 * transformation.transform({ it.files == [sourceFile] }, _ as ExecutionGraphDependenciesResolver, _) >> Try.successful(TransformationSubject.initial(sourceFile).createSubjectFromResult(ImmutableList.of(outFile3, outFile4))) 1 * visitor.visitArtifact(variant1DisplayName, targetAttributes, {it.file == outFile1}) 1 * visitor.visitArtifact(variant1DisplayName, targetAttributes, {it.file == outFile2}) diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultTransformerInvokerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultTransformerInvokerTest.groovy index 3aafa89d76bc4..8987a0a362263 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultTransformerInvokerTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultTransformerInvokerTest.groovy @@ -18,6 +18,7 @@ package org.gradle.api.internal.artifacts.transform import com.google.common.collect.ImmutableList import org.gradle.api.artifacts.transform.ArtifactTransform +import org.gradle.api.file.FileSystemLocation import org.gradle.api.internal.artifacts.DefaultBuildIdentifier import org.gradle.api.internal.artifacts.DefaultProjectComponentIdentifier import org.gradle.api.internal.artifacts.dsl.dependencies.ProjectFinder @@ -27,12 +28,14 @@ import org.gradle.api.internal.changedetection.state.DefaultWellKnownFileLocatio import org.gradle.api.internal.file.TestFiles import org.gradle.api.internal.project.ProjectInternal import org.gradle.api.internal.tasks.TaskDependencyResolveContext +import org.gradle.api.provider.Provider import org.gradle.api.tasks.FileNormalizer import org.gradle.internal.classloader.ClassLoaderHierarchyHasher import org.gradle.internal.component.local.model.ComponentFileArtifactIdentifier import org.gradle.internal.execution.TestExecutionHistoryStore import org.gradle.internal.fingerprint.AbsolutePathInputNormalizer import org.gradle.internal.fingerprint.FileCollectionFingerprinter +import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry import org.gradle.internal.fingerprint.impl.AbsolutePathFileCollectionFingerprinter import org.gradle.internal.fingerprint.impl.DefaultFileCollectionFingerprinterRegistry import org.gradle.internal.fingerprint.impl.OutputFileCollectionFingerprinter @@ -42,28 +45,28 @@ import org.gradle.internal.snapshot.impl.DefaultFileSystemMirror import org.gradle.internal.snapshot.impl.DefaultFileSystemSnapshotter import org.gradle.test.fixtures.AbstractProjectBuilderSpec import org.gradle.util.Path +import org.gradle.work.InputChanges import spock.lang.Unroll import java.util.function.BiFunction class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { - def workExecutorTestFixture = new WorkExecutorTestFixture() - def immutableTransformsStoreDirectory = temporaryFolder.file("output") def mutableTransformsStoreDirectory = temporaryFolder.file("child/build/transforms") + def executionHistoryStore = new TestExecutionHistoryStore() def fileSystemMirror = new DefaultFileSystemMirror(new DefaultWellKnownFileLocations([])) + def workExecutorTestFixture = new WorkExecutorTestFixture(fileSystemMirror) def fileSystemSnapshotter = new DefaultFileSystemSnapshotter(TestFiles.fileHasher(), new StringInterner(), TestFiles.fileSystem(), fileSystemMirror) - def executionHistoryStore = new TestExecutionHistoryStore() def transformationWorkspaceProvider = new TestTransformationWorkspaceProvider(immutableTransformsStoreDirectory, executionHistoryStore) def fileCollectionFactory = TestFiles.fileCollectionFactory() def artifactTransformListener = Mock(ArtifactTransformListener) def dependencyFingerprinter = new AbsolutePathFileCollectionFingerprinter(fileSystemSnapshotter) def outputFilesFingerprinter = new OutputFileCollectionFingerprinter(fileSystemSnapshotter) - def registry = new DefaultFileCollectionFingerprinterRegistry([dependencyFingerprinter, outputFilesFingerprinter]) + def fingerprinterRegistry = new DefaultFileCollectionFingerprinterRegistry([dependencyFingerprinter, outputFilesFingerprinter]) def classloaderHasher = Stub(ClassLoaderHierarchyHasher) { getClassLoaderHash(_) >> HashCode.fromInt(1234) @@ -91,11 +94,9 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { fileSystemSnapshotter, artifactTransformListener, transformationWorkspaceProvider, - registry, fileCollectionFactory, classloaderHasher, - projectFinder, - true + projectFinder ) private static class TestTransformer implements Transformer { @@ -126,14 +127,19 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { return false } + @Override + boolean requiresInputChanges() { + return false + } + @Override boolean isCacheable() { return false } @Override - ImmutableList transform(File inputArtifact, File outputDir, ArtifactTransformDependencies dependencies) { - return ImmutableList.copyOf(transformationAction.apply(inputArtifact, outputDir)) + ImmutableList transform(Provider inputArtifactProvider, File outputDir, ArtifactTransformDependencies dependencies, InputChanges inputChanges) { + return ImmutableList.copyOf(transformationAction.apply(inputArtifactProvider.get().asFile, outputDir)) } @Override @@ -152,7 +158,12 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { } @Override - void isolateParameters() { + boolean isIsolated() { + return true + } + + @Override + void isolateParameters(FileCollectionFingerprinterRegistry fingerprinterRegistry) { } @Override @@ -176,7 +187,7 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { } when: - def result = invoker.invoke(transformer, inputArtifact, dependencies, dependency(transformationType, inputArtifact)) + def result = invoker.invoke(transformer, inputArtifact, dependencies, dependency(transformationType, inputArtifact), fingerprinterRegistry) then: result.get().size() == 1 @@ -199,7 +210,7 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { } when: - invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact)) + invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact), fingerprinterRegistry) then: transformerInvocations == 1 @@ -207,7 +218,7 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { 1 * artifactTransformListener.afterTransformerInvocation(_, _) when: - invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact)) + invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact), fingerprinterRegistry) then: transformerInvocations == 1 1 * artifactTransformListener.beforeTransformerInvocation(_, _) @@ -231,7 +242,7 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { } when: - def result = invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact)) + def result = invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact), fingerprinterRegistry) then: transformerInvocations == 1 @@ -241,7 +252,7 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { wrappedFailure.cause == failure when: - invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact)) + invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact), fingerprinterRegistry) then: transformerInvocations == 2 1 * artifactTransformListener.beforeTransformerInvocation(_, _) @@ -262,7 +273,7 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { } when: - invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact)) + invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact), fingerprinterRegistry) then: transformerInvocations == 1 outputFile?.isFile() @@ -271,7 +282,7 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { outputFile.text = "changed" fileSystemMirror.beforeBuildFinished() - invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact)) + invoker.invoke(transformer, inputArtifact, dependencies, TransformationSubject.initial(inputArtifact), fingerprinterRegistry) then: transformerInvocations == 2 } @@ -292,8 +303,8 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { def subject = dependency(transformationType, inputArtifact) when: - invoker.invoke(transformer1, inputArtifact, dependencies, subject) - invoker.invoke(transformer2, inputArtifact, dependencies, subject) + invoker.invoke(transformer1, inputArtifact, dependencies, subject, fingerprinterRegistry) + invoker.invoke(transformer2, inputArtifact, dependencies, subject, fingerprinterRegistry) then: workspaces.size() == 2 @@ -317,14 +328,14 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { } def transformer = TestTransformer.create(HashCode.fromInt(1234), transformationAction) when: - invoker.invoke(transformer, inputArtifact1, dependencies, dependency(transformationType, inputArtifact1)) + invoker.invoke(transformer, inputArtifact1, dependencies, dependency(transformationType, inputArtifact1), fingerprinterRegistry) then: workspaces.size() == 1 when: fileSystemMirror.beforeBuildFinished() inputArtifact1.text = "changed" - invoker.invoke(transformer, inputArtifact2, dependencies, dependency(transformationType, inputArtifact2)) + invoker.invoke(transformer, inputArtifact2, dependencies, dependency(transformationType, inputArtifact2), fingerprinterRegistry) then: workspaces.size() == 2 @@ -347,14 +358,14 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { def subject = immutableDependency(inputArtifact) when: - invoker.invoke(transformer, inputArtifact, dependencies, subject) + invoker.invoke(transformer, inputArtifact, dependencies, subject, fingerprinterRegistry) then: workspaces.size() == 1 when: fileSystemMirror.beforeBuildFinished() inputArtifact.text = "changed" - invoker.invoke(transformer, inputArtifact, dependencies, subject) + invoker.invoke(transformer, inputArtifact, dependencies, subject, fingerprinterRegistry) then: workspaces.size() == 2 @@ -374,14 +385,14 @@ class DefaultTransformerInvokerTest extends AbstractProjectBuilderSpec { def subject = mutableDependency(inputArtifact) when: - invoker.invoke(transformer, inputArtifact, dependencies, subject) + invoker.invoke(transformer, inputArtifact, dependencies, subject, fingerprinterRegistry) then: workspaces.size() == 1 when: fileSystemMirror.beforeBuildFinished() inputArtifact.text = "changed" - invoker.invoke(transformer, inputArtifact, dependencies, subject) + invoker.invoke(transformer, inputArtifact, dependencies, subject, fingerprinterRegistry) then: workspaces.size() == 1 diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultVariantTransformRegistryTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultVariantTransformRegistryTest.groovy index 341ed0048475f..11a5cefd3721c 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultVariantTransformRegistryTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/DefaultVariantTransformRegistryTest.groovy @@ -16,10 +16,13 @@ package org.gradle.api.internal.artifacts.transform +import com.google.common.collect.ImmutableSet import org.gradle.api.artifacts.transform.ArtifactTransform -import org.gradle.api.artifacts.transform.AssociatedTransformAction +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.InputArtifactDependencies import org.gradle.api.artifacts.transform.TransformAction import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.artifacts.transform.VariantTransformConfigurationException import org.gradle.api.attributes.Attribute import org.gradle.api.internal.DynamicObjectAware @@ -38,7 +41,6 @@ import org.gradle.util.AttributeTestUtil import org.gradle.util.TestUtil import org.junit.Rule import spock.lang.Specification -import spock.lang.Unroll import javax.inject.Inject @@ -61,7 +63,7 @@ class DefaultVariantTransformRegistryTest extends Specification { def classLoaderHierarchyHasher = Mock(ClassLoaderHierarchyHasher) def attributesFactory = AttributeTestUtil.attributesFactory() def domainObjectContextProjectStateHandler = Mock(DomainObjectProjectStateHandler) - def registryFactory = new DefaultTransformationRegistrationFactory(isolatableFactory, classLoaderHierarchyHasher, transformerInvoker, valueSnapshotter, fileCollectionFactory, fileCollectionFingerprinterRegistry, domainObjectContextProjectStateHandler, new ArtifactTransformParameterScheme(instantiatorFactory.injectScheme(), inspectionScheme), new ArtifactTransformActionScheme(instantiatorFactory.injectScheme(), inspectionScheme, instantiatorFactory.injectScheme())) + def registryFactory = new DefaultTransformationRegistrationFactory(isolatableFactory, classLoaderHierarchyHasher, transformerInvoker, valueSnapshotter, fileCollectionFactory, fileCollectionFingerprinterRegistry, domainObjectContextProjectStateHandler, new ArtifactTransformParameterScheme(instantiatorFactory.injectScheme(), inspectionScheme), new ArtifactTransformActionScheme(instantiatorFactory.injectScheme(ImmutableSet.of(InputArtifact.class, InputArtifactDependencies.class)), inspectionScheme, instantiatorFactory.injectScheme())) def registry = new DefaultVariantTransformRegistry(instantiatorFactory, attributesFactory, Stub(ServiceRegistry), registryFactory, instantiatorFactory.injectScheme()) def "setup"() { @@ -137,13 +139,13 @@ class DefaultVariantTransformRegistryTest extends Specification { def registration = registry.transforms[0] registration.from.getAttribute(TEST_ATTRIBUTE) == "FROM" registration.to.getAttribute(TEST_ATTRIBUTE) == "TO" - registration.transformationStep.transformer.implementationClass == TestTransformAction - registration.transformationStep.transformer.parameterObject instanceof TestTransform + registration.transformationStep.transformer.implementationClass == TestTransform + registration.transformationStep.transformer.parameterObject instanceof TestTransform.Parameters } - def "creates registration with with action"() { + def "creates registration for parametereless action"() { when: - registry.registerTransformAction(TestTransformAction) { + registry.registerTransform(ParameterlessTestTransform) { it.from.attribute(TEST_ATTRIBUTE, "FROM") it.to.attribute(TEST_ATTRIBUTE, "TO") } @@ -153,10 +155,52 @@ class DefaultVariantTransformRegistryTest extends Specification { def registration = registry.transforms[0] registration.from.getAttribute(TEST_ATTRIBUTE) == "FROM" registration.to.getAttribute(TEST_ATTRIBUTE) == "TO" - registration.transformationStep.transformer.implementationClass == TestTransformAction + registration.transformationStep.transformer.implementationClass == ParameterlessTestTransform registration.transformationStep.transformer.parameterObject == null } + def "cannot use TransformParameters as parameter type"() { + when: + registry.registerTransform(UnspecifiedTestTransform) { + it.from.attribute(TEST_ATTRIBUTE, "FROM") + it.to.attribute(TEST_ATTRIBUTE, "TO") + } + + then: + def e = thrown(VariantTransformConfigurationException) + e.message == 'Could not register transform: must use a sub-type of TransformParameters as parameter type. Use TransformParameters.None for transforms without parameters.' + e.cause == null + } + + def "cannot configure parameters for parameterless action"() { + when: + registry.registerTransform(ParameterlessTestTransform) { + it.from.attribute(TEST_ATTRIBUTE, "FROM") + it.to.attribute(TEST_ATTRIBUTE, "TO") + it.parameters { + } + } + + then: + def e = thrown(VariantTransformConfigurationException) + e.message == 'Cannot configure parameters for artifact transform without parameters.' + e.cause == null + } + + def "cannot query parameters object for parameterless action"() { + when: + registry.registerTransform(ParameterlessTestTransform) { + it.from.attribute(TEST_ATTRIBUTE, "FROM") + it.to.attribute(TEST_ATTRIBUTE, "TO") + it.parameters + } + + then: + def e = thrown(VariantTransformConfigurationException) + e.message == 'Cannot query parameters for artifact transform without parameters.' + e.cause == null + } + def "delegates are DSL decorated but not extensible when registering with config object"() { def registration @@ -200,17 +244,6 @@ class DefaultVariantTransformRegistryTest extends Specification { e.cause == null } - def "fails when registering unannotated parameter type"() { - when: - registry.registerTransform(UnAnnotatedTestTransformConfig) { - } - - then: - def e = thrown(VariantTransformConfigurationException) - e.message == 'Could not register transform: an artifact transform action must be provided.' - e.cause == null - } - def "fails when multiple artifactTransforms are provided for registration"() { when: registry.registerTransform { @@ -237,10 +270,9 @@ class DefaultVariantTransformRegistryTest extends Specification { e.cause == null } - @Unroll - def "fails when no from attributes are provided for #method"() { + def "fails when no from attributes are provided for registerTransform"() { when: - registry."$method"(argument) { + registry.registerTransform(TestTransform) { it.to.attribute(TEST_ATTRIBUTE, "to") } @@ -248,11 +280,6 @@ class DefaultVariantTransformRegistryTest extends Specification { def e = thrown(VariantTransformConfigurationException) e.message == "Could not register transform: at least one 'from' attribute must be provided." e.cause == null - - where: - method | argument - "registerTransform" | TestTransform - "registerTransformAction" | TestTransformAction } def "fails when no to attributes are provided for legacy registration"() { @@ -268,10 +295,9 @@ class DefaultVariantTransformRegistryTest extends Specification { e.cause == null } - @Unroll - def "fails when no to attributes are provided for #method"() { + def "fails when no to attributes are provided for registerTransform"() { when: - registry."${method}"(argument) { + registry.registerTransform(TestTransform) { it.from.attribute(TEST_ATTRIBUTE, "from") } @@ -279,11 +305,6 @@ class DefaultVariantTransformRegistryTest extends Specification { def e = thrown(VariantTransformConfigurationException) e.message == "Could not register transform: at least one 'to' attribute must be provided." e.cause == null - - where: - method | argument - "registerTransform" | TestTransform - "registerTransformAction" | TestTransformAction } def "fails when to attributes are not a subset of from attributes for legacy registration"() { @@ -302,10 +323,9 @@ class DefaultVariantTransformRegistryTest extends Specification { e.cause == null } - @Unroll - def "fails when to attributes are not a subset of from attributes for #method"() { + def "fails when to attributes are not a subset of from attributes for registerTransform"() { when: - registry."$method"(argument) { + registry.registerTransform(TestTransform) { it.from.attribute(TEST_ATTRIBUTE, "from") it.from.attribute(Attribute.of("from2", String), "from") it.to.attribute(TEST_ATTRIBUTE, "to") @@ -316,16 +336,6 @@ class DefaultVariantTransformRegistryTest extends Specification { def e = thrown(VariantTransformConfigurationException) e.message == "Could not register transform: each 'to' attribute must be included as a 'from' attribute." e.cause == null - - where: - method | argument - "registerTransform" | TestTransform - "registerTransformAction" | TestTransformAction - } - - @AssociatedTransformAction(TestTransformAction) - static class TestTransform { - String value } static class UnAnnotatedTestTransformConfig { @@ -339,7 +349,23 @@ class DefaultVariantTransformRegistryTest extends Specification { } } - static class TestTransformAction implements TransformAction { + static abstract class TestTransform implements TransformAction { + static class Parameters implements TransformParameters { + String value + } + + @Override + void transform(TransformOutputs outputs) { + } + } + + static abstract class ParameterlessTestTransform implements TransformAction { + @Override + void transform(TransformOutputs outputs) { + } + } + + static abstract class UnspecifiedTestTransform implements TransformAction { @Override void transform(TransformOutputs outputs) { } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformationMatchingSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformationMatchingSpec.groovy index c5ea15dc7e006..93eaa1d038559 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformationMatchingSpec.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformationMatchingSpec.groovy @@ -16,14 +16,18 @@ package org.gradle.api.internal.artifacts.transform +import org.gradle.internal.fingerprint.FileCollectionFingerprinterRegistry import spock.lang.Specification class TransformationMatchingSpec extends Specification { + def projectStateHandler = Mock(DomainObjectProjectStateHandler) + def fileCollectionFingerprinterRegistry = Mock(FileCollectionFingerprinterRegistry) + def "different TransformationStep does not contain each other"() { given: - def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) - def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) + def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) + def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) expect: !step1.endsWith(step2) @@ -32,7 +36,7 @@ class TransformationMatchingSpec extends Specification { def "TransformationStep contains itself"() { given: - def step = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) + def step = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) expect: step.endsWith(step) @@ -40,8 +44,8 @@ class TransformationMatchingSpec extends Specification { def "chain contains its final step"() { given: - def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) - def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) + def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) + def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) def chain = new TransformationChain(step1, step2) expect: @@ -54,8 +58,8 @@ class TransformationMatchingSpec extends Specification { def "chain contains itself"() { given: - def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) - def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) + def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) + def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) def chain = new TransformationChain(step1, step2) expect: @@ -64,9 +68,9 @@ class TransformationMatchingSpec extends Specification { def "longer chain contains shorter chain"() { given: - def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) - def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) - def step3 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) + def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) + def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) + def step3 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) def subChain = new TransformationChain(step2, step3) def longChain = new TransformationChain(new TransformationChain(step1, step2), step3) @@ -77,9 +81,9 @@ class TransformationMatchingSpec extends Specification { def "different chains do not contain each other"() { given: - def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) - def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) - def step3 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker)) + def step1 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) + def step2 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) + def step3 = new TransformationStep(Mock(Transformer), Mock(TransformerInvoker), projectStateHandler, fileCollectionFingerprinterRegistry) def chain1 = new TransformationChain(step2, step3) def chain2 = new TransformationChain(step1, step2) def chain3 = new TransformationChain(step1, step3) @@ -91,8 +95,5 @@ class TransformationMatchingSpec extends Specification { !chain3.endsWith(chain1) !chain2.endsWith(chain3) !chain1.endsWith(chain3) - - - } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformationNodeSpec.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformationNodeSpec.groovy index e779b92a62153..1d23992b3736e 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformationNodeSpec.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformationNodeSpec.groovy @@ -118,6 +118,11 @@ class TransformationNodeSpec extends Specification { return true } + @Override + boolean requiresMonitoring() { + return false + } + @Override String toString() { return null diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformingAsyncArtifactListenerTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformingAsyncArtifactListenerTest.groovy index 5aa26394afd62..4be8ef342405b 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformingAsyncArtifactListenerTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/TransformingAsyncArtifactListenerTest.groovy @@ -16,9 +16,11 @@ package org.gradle.api.internal.artifacts.transform +import com.google.common.collect.ImmutableList import com.google.common.collect.Maps import org.gradle.api.artifacts.component.ComponentArtifactIdentifier import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvableArtifact +import org.gradle.internal.Try import org.gradle.internal.operations.BuildOperation import org.gradle.internal.operations.BuildOperationQueue import org.gradle.testing.internal.util.Specification @@ -26,14 +28,16 @@ import org.gradle.testing.internal.util.Specification class TransformingAsyncArtifactListenerTest extends Specification { def transformation = Mock(Transformation) def operationQueue = Mock(BuildOperationQueue) - def listener = new TransformingAsyncArtifactListener(transformation, null, operationQueue, Maps.newHashMap(), Maps.newHashMap(), Mock(ExecutionGraphDependenciesResolver)) + def transformationNodeRegistry = Mock(TransformationNodeRegistry) + def listener = new TransformingAsyncArtifactListener(transformation, null, operationQueue, Maps.newHashMap(), Maps.newHashMap(), Mock(ExecutionGraphDependenciesResolver), transformationNodeRegistry) def file = new File("foo") def artifactFile = new File("foo-artifact") def artifactId = Stub(ComponentArtifactIdentifier) def artifact = Stub(ResolvableArtifact) { getId() >> artifactId - getArtifactFile() >> artifactFile + getFile() >> artifactFile } + def node = Mock(TransformationNode) def "adds file transformations to the build operation queue"() { when: @@ -43,11 +47,22 @@ class TransformingAsyncArtifactListenerTest extends Specification { 1 * operationQueue.add(_ as BuildOperation) } - def "runs artifact transformations immediately"() { + def "runs artifact transformations immediately when not scheduled"() { when: listener.artifactAvailable(artifact) then: - 1 * transformation.transform({ it.files == [artifactFile] }, _ as ExecutionGraphDependenciesResolver) + 1 * transformationNodeRegistry.getCompleted(artifactId, transformation) >> Optional.empty() + 1 * transformation.transform({ it.files == [artifactFile] }, _ as ExecutionGraphDependenciesResolver, _) + } + + def "re-uses scheduled artifact transformation result"() { + when: + listener.artifactAvailable(artifact) + + then: + 1 * transformationNodeRegistry.getCompleted(artifactId, transformation) >> Optional.of(node) + 1 * node.getTransformedSubject() >> Try.successful(TransformationSubject.initial(artifact.id, artifact.file).createSubjectFromResult(ImmutableList.of())) + 0 * transformation.transform(_, _ as ExecutionGraphDependenciesResolver, _) } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/WorkExecutorTestFixture.java b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/WorkExecutorTestFixture.java index a264b352ddcae..a113c6693bf92 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/WorkExecutorTestFixture.java +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/api/internal/artifacts/transform/WorkExecutorTestFixture.java @@ -22,18 +22,22 @@ import org.gradle.caching.internal.controller.BuildCacheStoreCommand; import org.gradle.initialization.BuildCancellationToken; import org.gradle.initialization.DefaultBuildCancellationToken; +import org.gradle.internal.execution.CachingResult; +import org.gradle.internal.execution.IncrementalContext; import org.gradle.internal.execution.OutputChangeListener; import org.gradle.internal.execution.WorkExecutor; import org.gradle.internal.execution.history.OutputFilesRepository; -import org.gradle.internal.execution.impl.steps.UpToDateResult; +import org.gradle.internal.execution.history.changes.DefaultExecutionStateChangeDetector; import org.gradle.internal.execution.timeout.impl.DefaultTimeoutHandler; import org.gradle.internal.id.UniqueId; +import org.gradle.internal.scan.config.BuildScanPluginApplied; import org.gradle.internal.scopeids.id.BuildInvocationScopeId; import org.gradle.internal.service.scopes.ExecutionGradleServices; import org.gradle.internal.snapshot.FileSystemSnapshot; +import org.gradle.internal.snapshot.impl.DefaultFileSystemMirror; -import javax.annotation.Nullable; import java.io.File; +import java.util.Optional; public class WorkExecutorTestFixture { @@ -48,10 +52,9 @@ public boolean isEmitDebugLogging() { return false; } - @Nullable @Override - public T load(BuildCacheLoadCommand command) { - return null; + public Optional load(BuildCacheLoadCommand command) { + return Optional.empty(); } @Override @@ -66,17 +69,19 @@ public void close() { }; private BuildInvocationScopeId buildInvocationScopeId = new BuildInvocationScopeId(UniqueId.generate()); private BuildCancellationToken cancellationToken = new DefaultBuildCancellationToken(); - private final WorkExecutor workExecutor; + private final WorkExecutor workExecutor; - WorkExecutorTestFixture() { + WorkExecutorTestFixture(DefaultFileSystemMirror fileSystemMirror) { BuildCacheCommandFactory buildCacheCommandFactory = null; OutputChangeListener outputChangeListener = new OutputChangeListener() { @Override public void beforeOutputChange() { + fileSystemMirror.beforeOutputChange(); } @Override public void beforeOutputChange(Iterable affectedOutputPaths) { + fileSystemMirror.beforeOutputChange(affectedOutputPaths); } }; OutputFilesRepository outputFilesRepository = new OutputFilesRepository() { @@ -89,18 +94,26 @@ public boolean isGeneratedByGradle(File file) { public void recordOutputs(Iterable outputFileFingerprints) { } }; + BuildScanPluginApplied buildScanPluginApplied = new BuildScanPluginApplied() { + @Override + public boolean isBuildScanPluginApplied() { + return false; + } + }; workExecutor = new ExecutionGradleServices().createWorkExecutor( - buildCacheController, buildCacheCommandFactory, - buildInvocationScopeId, + buildCacheController, + buildScanPluginApplied, cancellationToken, + buildInvocationScopeId, + new DefaultExecutionStateChangeDetector(), outputChangeListener, outputFilesRepository, new DefaultTimeoutHandler(null) ); } - public WorkExecutor getWorkExecutor() { + public WorkExecutor getWorkExecutor() { return workExecutor; } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultMavenModuleResolveMetadataTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultMavenModuleResolveMetadataTest.groovy index d2cd459c9bc30..f08c8587ffbdb 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultMavenModuleResolveMetadataTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/external/model/DefaultMavenModuleResolveMetadataTest.groovy @@ -21,11 +21,11 @@ import org.gradle.api.artifacts.ModuleVersionIdentifier import org.gradle.api.artifacts.component.ModuleComponentIdentifier import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.Category import org.gradle.api.attributes.Usage import org.gradle.api.internal.artifacts.DefaultImmutableModuleIdentifierFactory import org.gradle.api.internal.artifacts.DefaultModuleIdentifier import org.gradle.api.internal.artifacts.dependencies.DefaultMutableVersionConstraint -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport import org.gradle.api.internal.artifacts.repositories.metadata.DefaultMavenImmutableAttributesFactory import org.gradle.api.internal.artifacts.repositories.metadata.MavenMutableModuleMetadataFactory import org.gradle.api.internal.model.NamedObjectInstantiator @@ -147,7 +147,7 @@ class DefaultMavenModuleResolveMetadataTest extends AbstractLazyModuleComponentR def "recognises java library for packaging=#packaging"() { given: def stringUsageAttribute = Attribute.of(Usage.USAGE_ATTRIBUTE.getName(), String.class) - def componentTypeAttribute = PlatformSupport.COMPONENT_CATEGORY + def componentTypeAttribute = Attribute.of(Category.CATEGORY_ATTRIBUTE.getName(), String.class) def metadata = new DefaultMutableMavenModuleResolveMetadata(Mock(ModuleVersionIdentifier), id, [], new DefaultMavenImmutableAttributesFactory(AttributeTestUtil.attributesFactory(), NamedObjectInstantiator.INSTANCE), TestUtil.objectInstantiator()) metadata.packaging = packaging metadata.variantMetadataRules.variantDerivationStrategy = new JavaEcosystemVariantDerivationStrategy() diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/AttributeConfigurationSelectorTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/AttributeConfigurationSelectorTest.groovy index 38412c12e0b58..ad97b27c5f613 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/AttributeConfigurationSelectorTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/component/model/AttributeConfigurationSelectorTest.groovy @@ -18,6 +18,7 @@ package org.gradle.internal.component.model import com.google.common.base.Optional import com.google.common.collect.ImmutableList +import org.gradle.api.artifacts.ArtifactIdentifier import org.gradle.api.artifacts.ModuleVersionIdentifier import org.gradle.api.artifacts.component.ComponentIdentifier import org.gradle.api.attributes.Attribute @@ -30,6 +31,8 @@ import org.gradle.api.internal.attributes.DefaultAttributesSchema import org.gradle.api.internal.attributes.ImmutableAttributes import org.gradle.internal.component.AmbiguousConfigurationSelectionException import org.gradle.internal.component.NoMatchingConfigurationSelectionException +import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier +import org.gradle.internal.component.external.model.ModuleComponentArtifactMetadata import org.gradle.util.SnapshotTestUtil import org.gradle.util.TestUtil import org.gradle.util.TextUtil @@ -45,6 +48,7 @@ class AttributeConfigurationSelectorTest extends Specification { private ConfigurationMetadata selected private ImmutableAttributes consumerAttributes = ImmutableAttributes.EMPTY private List requestedCapabilities = [] + private List artifacts = [] @Unroll def "selects a variant when there's no ambiguity"() { @@ -187,7 +191,7 @@ All of them match the consumer attributes: then: AmbiguousConfigurationSelectionException e = thrown() - failsWith(e,'''Cannot choose between the following variants of org:lib:1.0: + failsWith(e, '''Cannot choose between the following variants of org:lib:1.0: - api1 - api2 - api3 @@ -283,15 +287,55 @@ All of them match the consumer attributes: selected.name == 'second' } + def "should select the variant which matches the requested classifier"() { + def variant1 = variant("first", ImmutableAttributes.EMPTY) + def variant2 = variant("second", ImmutableAttributes.EMPTY) + + given: + variant1.getArtifacts() >> [ + artifact('foo', null) + ] + variant2.getArtifacts() >> [ + artifact('foo', 'classy') + ] + component(variant1, variant2) + + and: + requireArtifact('foo', 'jar', 'jar', 'classy') + + when: + performSelection() + + then: + selected.name == 'second' + } + private void performSelection() { selected = AttributeConfigurationSelector.selectConfigurationUsingAttributeMatching( consumerAttributes, requestedCapabilities, targetComponent, - attributesSchema + attributesSchema, + artifacts ) } + private void requireArtifact(String name = "foo", String type = "jar", String ext = "jar", String classifier = null) { + artifacts << new DefaultIvyArtifactName(name, type, ext, classifier) + } + + private ModuleComponentArtifactMetadata artifact(String name, String classifier) { + Stub(ModuleComponentArtifactMetadata) { + getId() >> Stub(ModuleComponentArtifactIdentifier) + toArtifactIdentifier() >> Stub(ArtifactIdentifier) { + getName() >> name + getType() >> "jar" + getExtension() >> "jar" + getClassifier() >> classifier + } + } + } + private consumerAttributes(Map attrs) { this.consumerAttributes = attributes(attrs) } @@ -349,7 +393,7 @@ All of them match the consumer attributes: @Override void execute(CompatibilityCheckDetails details) { - if (details.consumerValue == 'java-api' && details.producerValue=='java-runtime') { + if (details.consumerValue == 'java-api' && details.producerValue == 'java-runtime') { details.compatible() } } diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentIdResolveResultTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentIdResolveResultTest.groovy index c6e76d89944e4..56bd149a91d1c 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentIdResolveResultTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentIdResolveResultTest.groovy @@ -61,7 +61,8 @@ class DefaultBuildableComponentIdResolveResultTest extends Specification { } def "can mark as failed"() { - def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), "broken") + org.gradle.internal.Factory broken = { "too bad" } + def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), broken) when: result.failed(failure) diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResultTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResultTest.groovy index 354572be7d14d..525e421bb7809 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResultTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableComponentResolveResultTest.groovy @@ -73,7 +73,8 @@ class DefaultBuildableComponentResolveResultTest extends Specification { } def "cannot get id when resolve failed"() { - def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), "broken") + org.gradle.internal.Factory broken = { "too bad" } + def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), broken) when: result.failed(failure) @@ -85,7 +86,8 @@ class DefaultBuildableComponentResolveResultTest extends Specification { } def "cannot get meta-data when resolve failed"() { - def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), "broken") + org.gradle.internal.Factory broken = { "too bad" } + def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), broken) when: result.failed(failure) @@ -137,8 +139,9 @@ class DefaultBuildableComponentResolveResultTest extends Specification { } def "copies failure result to an id resolve result"() { + org.gradle.internal.Factory broken = { "too bad" } def idResult = Mock(BuildableComponentIdResolveResult) - def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), "broken") + def failure = new ModuleVersionResolveException(Stub(ModuleVersionSelector), broken) given: result.attempted("a") diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableModuleComponentMetaDataResolveResultTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableModuleComponentMetaDataResolveResultTest.groovy index 751c19ecd66e3..eec02f9f65fd2 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableModuleComponentMetaDataResolveResultTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableModuleComponentMetaDataResolveResultTest.groovy @@ -44,7 +44,8 @@ class DefaultBuildableModuleComponentMetaDataResolveResultTest extends Specifica } def "can mark as failed"() { - def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), "broken") + org.gradle.internal.Factory broken = { "too bad" } + def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), broken) when: descriptor.failed(failure) @@ -112,8 +113,9 @@ class DefaultBuildableModuleComponentMetaDataResolveResultTest extends Specifica } def "cannot get meta-data when failed"() { + org.gradle.internal.Factory broken = { "too bad" } given: - def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), "broken") + def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), broken) descriptor.failed(failure) when: diff --git a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableModuleVersionListingResolveResultTest.groovy b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableModuleVersionListingResolveResultTest.groovy index 6bf5eda266e05..196e9d0389746 100644 --- a/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableModuleVersionListingResolveResultTest.groovy +++ b/subprojects/dependency-management/src/test/groovy/org/gradle/internal/resolve/result/DefaultBuildableModuleVersionListingResolveResultTest.groovy @@ -45,7 +45,8 @@ class DefaultBuildableModuleVersionListingResolveResultTest extends Specificatio } def "can mark as failed"() { - def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), "broken") + org.gradle.internal.Factory broken = { "too bad" } + def failure = new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId("a", "b"), "c"), broken) when: descriptor.failed(failure) diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/boolean-attributes.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/boolean-attributes.module index 638f567b8a3ed..7b104104ac0e0 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/boolean-attributes.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/boolean-attributes.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "builtBy": { "gradle": { "version": "123", "buildId": "abc" } }, "variants": [ { diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/dependencies-with-attributes.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/dependencies-with-attributes.module index d5e877a34eea4..61a2e9ad33d00 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/dependencies-with-attributes.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/dependencies-with-attributes.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/java-library-with-excludes.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/java-library-with-excludes.module index 04b99253f4059..fd86eab36a932 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/java-library-with-excludes.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/java-library-with-excludes.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "component": { "group": "org.gradle.test", "module": "publishTest", diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-capabilities.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-capabilities.module index 22c7d8f825d50..6291f65e0b7e3 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-capabilities.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-capabilities.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-constraints.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-constraints.module index 50ba1cad77354..be74f29da84bc 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-constraints.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-constraints.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-dependencies.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-dependencies.module index f288d9488a32d..88fdf10270891 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-dependencies.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-dependencies.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-variants.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-variants.module index f8e39e1b2505b..4b885c14e1fd9 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-variants.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/module-with-variants.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "variants": [ { "name": "api", diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/with-custom-attributes.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/with-custom-attributes.module index e8f2756eaacc7..fb2883e8cebe2 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/with-custom-attributes.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/with-custom-attributes.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "component": { "group": "org.gradle.test", "module": "publishTest", diff --git a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/with-nondefault-status.module b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/with-nondefault-status.module index 684af2c794b54..ee4092c42f4f9 100644 --- a/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/with-nondefault-status.module +++ b/subprojects/dependency-management/src/test/resources/org/gradle/api/internal/artifacts/ivyservice/modulecache/ModuleMetadataSerializerTest/gradle/with-nondefault-status.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "component": { "group": "org.gradle.test", "module": "publishTest", diff --git a/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy b/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy index 5ac6e1e476aad..64f740531eb97 100644 --- a/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy +++ b/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/api/internal/artifacts/result/ResolutionResultDataBuilder.groovy @@ -40,7 +40,8 @@ class ResolutionResultDataBuilder { static DefaultUnresolvedDependencyResult newUnresolvedDependency(String group='x', String module='x', String version='1', String selectedVersion='1') { def requested = newSelector(group, module, version) - new DefaultUnresolvedDependencyResult(requested, false, ComponentSelectionReasons.requested(), newModule(group, module, selectedVersion), new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId(group, module), version), "broken")) + org.gradle.internal.Factory broken = { "broken" } + new DefaultUnresolvedDependencyResult(requested, false, ComponentSelectionReasons.requested(), newModule(group, module, selectedVersion), new ModuleVersionResolveException(newSelector(DefaultModuleIdentifier.newId(group, module), version), broken)) } static DefaultResolvedComponentResult newModule(String group='a', String module='a', String version='1', ComponentSelectionReason selectionReason = ComponentSelectionReasons.requested(), ResolvedVariantResult variant = newVariant(), String repoId = null) { diff --git a/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/ModuleVersionSpec.groovy b/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/ModuleVersionSpec.groovy index 55e263fc46ae5..fa8dc6aaa3309 100644 --- a/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/ModuleVersionSpec.groovy +++ b/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/ModuleVersionSpec.groovy @@ -297,6 +297,10 @@ class ModuleVersionSpec { capabilities = variant.capabilities.collect { new CapabilitySpec(group: it.group, name: it.name, version: it.version) } + if (variant.noArtifacts) { + artifacts = [] + useDefaultArtifacts = false + } } } } diff --git a/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/VariantSpec.groovy b/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/VariantSpec.groovy index a64e0e73733a2..98e022639a305 100644 --- a/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/VariantSpec.groovy +++ b/subprojects/dependency-management/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/VariantSpec.groovy @@ -26,6 +26,7 @@ class VariantSpec { Map attributes = [:] List artifacts = [] List capabilities = [] + boolean noArtifacts = false void dependsOn(coord) { dependsOn << coord diff --git a/subprojects/diagnostics/diagnostics.gradle.kts b/subprojects/diagnostics/diagnostics.gradle.kts index c84dcd6c756fd..484da75b20272 100644 --- a/subprojects/diagnostics/diagnostics.gradle.kts +++ b/subprojects/diagnostics/diagnostics.gradle.kts @@ -32,7 +32,6 @@ dependencies { implementation(project(":baseServicesGroovy")) implementation(library("guava")) implementation(library("jatl")) - implementation(library("commons_collections")) implementation(library("commons_lang")) integTestRuntimeOnly(project(":plugins")) diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportIntegrationTest.groovy index 361289808c52b..567663bfee8fd 100644 --- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportIntegrationTest.groovy +++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/reporting/model/ModelReportIntegrationTest.groovy @@ -40,6 +40,7 @@ class ModelReportIntegrationTest extends AbstractIntegrationSpec { help() init() model() + prepareKotlinBuildScriptModel() projects() properties() tasks() @@ -337,6 +338,12 @@ model { | Creator: \tProject..tasks.model() | Rules: ⤷ copyToTaskContainer + + prepareKotlinBuildScriptModel + | Type: \torg.gradle.api.DefaultTask + | Value: \ttask ':prepareKotlinBuildScriptModel\' + | Creator: \tProject..tasks.prepareKotlinBuildScriptModel() + | Rules: + ⤷ copyToTaskContainer + projects | Type: \torg.gradle.api.tasks.diagnostics.ProjectReportTask | Value: \ttask ':projects\' diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy index 0d57b07fe5d27..d45985bc04113 100644 --- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy +++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportTaskIntegrationTest.groovy @@ -17,6 +17,7 @@ package org.gradle.api.tasks.diagnostics import groovy.transform.CompileStatic +import org.gradle.api.JavaVersion import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.FeaturePreviewsFixture import org.gradle.integtests.fixtures.resolve.ResolveTestFixture @@ -146,9 +147,9 @@ No dependencies matching given input were found in configuration ':conf' outputContains """ org:leaf2:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf2:1.0 @@ -200,9 +201,9 @@ org:leaf2:1.0 Task :dependencyInsight org:leaf2:2.5 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - By conflict resolution : between versions 1.5, 2.5 and 1.0 @@ -256,9 +257,9 @@ org:leaf2:1.5 -> 2.5 outputContains """ org:leaf:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:1.0 @@ -278,9 +279,9 @@ org:leaf:1.0 outputContains """ org:leaf:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:1.0 @@ -334,9 +335,9 @@ org:leaf:1.0 org:leaf2:2.5 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - By conflict resolution : between versions 1.5, 2.5 and 1.0 @@ -407,9 +408,9 @@ org:leaf2:1.5 -> 2.5 org:leaf2:2.5 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - By conflict resolution : between versions 1.5, 2.5 and 1.0 @@ -579,9 +580,9 @@ org:foo:1.+ FAILED outputContains """ org:leaf:1.0 (forced) variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:1.0 @@ -600,9 +601,9 @@ org:leaf:2.0 -> 1.0 outputContains """ org:leaf:1.0 (selected by rule) variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:1.0 @@ -643,9 +644,9 @@ org:leaf:2.0 -> 1.0 outputContains """ org:leaf:2 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Was requested : didn't match version 3 because testing stuff @@ -755,9 +756,9 @@ org:leaf:latest.integration -> 1.0 outputContains """ org:bar:2.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Forced @@ -818,9 +819,9 @@ org:foo:1.0 -> org:bar:2.0 outputContains """ org.test:bar:2.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Selected by rule : why not? @@ -830,9 +831,9 @@ org:bar:1.0 -> org.test:bar:2.0 org:baz:1.0 (selected by rule) variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:baz:1.0 @@ -840,9 +841,9 @@ org:baz:1.0 org:foo:2.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Selected by rule : because I am in control @@ -888,9 +889,9 @@ org:foo:1.0 -> 2.0 outputContains """ org:baz:2.0 (selected by rule) variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:baz:1.1 -> 2.0 @@ -898,9 +899,9 @@ org:baz:1.1 -> 2.0 org:bar:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Selected by rule : foo superseded by bar @@ -945,9 +946,9 @@ org:foo:1.0 -> org:bar:1.0 outputContains """ org:new-leaf:77 (selected by rule) variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:1.0 -> org:new-leaf:77 @@ -994,9 +995,9 @@ org:leaf:2.0 -> org:new-leaf:77 then: outputContains """org:bar:2.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Selected by rule : I am not sure I want to explain @@ -1006,9 +1007,9 @@ org:bar:1.0 -> 2.0 org:foo:2.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Selected by rule : I want to @@ -1099,9 +1100,9 @@ org:leaf:latest.integration -> 1.6 outputContains """ org:leaf:2.0 (forced) variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:2.0 @@ -1147,9 +1148,9 @@ org:leaf:1.0 -> 2.0 outputContains """ org:leaf:1.5 (forced) variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:1.0 -> 1.5 @@ -1196,9 +1197,9 @@ org:leaf:2.0 -> 1.5 outputContains """ org:leaf:1.0 (forced) variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:1.0 @@ -1244,9 +1245,9 @@ org:leaf:2.0 -> 1.0 outputContains """ org:leaf:2.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] Selection reasons: - Forced @@ -1657,9 +1658,9 @@ project :C FAILED outputContains """ org:leaf2:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf2:1.0 @@ -1704,6 +1705,8 @@ project : variant "runtimeElements" [ org.gradle.usage = java-runtime-jars (not requested) org.gradle.dependency.bundling = external (not requested) + org.gradle.category = library (not requested) + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} (not requested) ] project : @@ -1750,9 +1753,9 @@ project : outputContains """ org:leaf2:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf2:1.0 @@ -1802,6 +1805,8 @@ project :impl variant "runtimeElements" [ org.gradle.usage = java-runtime-jars (not requested) org.gradle.dependency.bundling = external (not requested) + org.gradle.category = library (not requested) + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} (not requested) ] project :impl @@ -1852,10 +1857,11 @@ org:leaf4:1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] org:leaf4:1.0 @@ -1889,10 +1895,11 @@ org:leaf1:1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] org:leaf1:1.0 @@ -1908,10 +1915,11 @@ org:leaf2:1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] org:leaf2:1.0 @@ -1968,6 +1976,8 @@ project :api variant "apiElements" [ org.gradle.usage = java-api-jars (compatible with: java-api) org.gradle.dependency.bundling = external + org.gradle.category = library (not requested) + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] project :api @@ -1984,6 +1994,8 @@ project :some:deeply:nested variant "apiElements" [ org.gradle.usage = java-api-jars (compatible with: java-api) org.gradle.dependency.bundling = external + org.gradle.category = library (not requested) + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] project :some:deeply:nested @@ -1999,6 +2011,8 @@ project :some:deeply:nested variant "apiElements" [ org.gradle.usage = java-api-jars (compatible with: java-api) org.gradle.dependency.bundling = external + org.gradle.category = library (not requested) + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] project :some:deeply:nested @@ -2048,10 +2062,11 @@ org:leaf3:1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] org:leaf3:1.0 @@ -2095,9 +2110,9 @@ org:leaf3:1.0 then: result.groupedOutput.task(":dependencyInsight").output.contains("""foo:bar:2.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] foo:bar:2.0 @@ -2105,9 +2120,9 @@ foo:bar:2.0 foo:foo:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] foo:foo:1.0 @@ -2151,10 +2166,11 @@ foo:foo:1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - By constraint : $rejected @@ -2206,10 +2222,11 @@ org:foo -> $selected variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - By constraint : ${rejected}${reason} @@ -2258,10 +2275,11 @@ org:foo -> $selected variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Was requested : ${rejected}${reason} @@ -2307,10 +2325,11 @@ org:foo:${displayVersion} -> $selected variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Was requested : didn't match versions 2.0, 1.5, 1.4 @@ -2362,10 +2381,11 @@ org:foo:[1.1,1.3] -> 1.3 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Was requested : rejected versions 1.2, 1.1 @@ -2377,10 +2397,11 @@ org:foo:1.1 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Was requested : rejected version 1.2 @@ -2436,10 +2457,11 @@ org:bar:1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Rejection : 1.2 by rule because version 1.2 is bad @@ -2452,10 +2474,11 @@ org:foo:1.1 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Was requested : rejected version 1.2 @@ -2496,10 +2519,11 @@ org:leaf:1.0 (by constraint) variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] org:leaf:1.0 @@ -2548,10 +2572,11 @@ org.test:leaf:1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Was requested : first reason @@ -2594,9 +2619,9 @@ org.test:leaf:1.0 outputContains """ org:leaf2:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf2:1.0 @@ -2729,10 +2754,11 @@ org:foo:1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Was requested : rejected versions 1.2, 1.1 @@ -2785,10 +2811,11 @@ org:foo:{require [1.0,); reject 1.1} -> 1.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Was requested : first reason @@ -2850,19 +2877,22 @@ org:foo:1.0 color = blue org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - Rejection : version 1.2: - Attribute 'color' didn't match. Requested 'blue', was: 'red' - Attribute 'org.gradle.dependency.bundling' didn't match. Requested 'external', was: not found + - Attribute 'org.gradle.jvm.version' didn't match. Requested '${JavaVersion.current().majorVersion}', was: not found - Attribute 'org.gradle.usage' didn't match. Requested 'java-api', was: not found - Rejection : version 1.1: - Attribute 'color' didn't match. Requested 'blue', was: 'green' - Attribute 'org.gradle.dependency.bundling' didn't match. Requested 'external', was: not found + - Attribute 'org.gradle.jvm.version' didn't match. Requested '${JavaVersion.current().majorVersion}', was: not found - Attribute 'org.gradle.usage' didn't match. Requested 'java-api', was: not found org:foo:[1.0,) -> 1.0 @@ -2922,10 +2952,11 @@ planet:mercury:1.0.2 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - By conflict resolution : between versions 1.0.2 and 1.0.1 @@ -2952,10 +2983,11 @@ planet:venus:2.0.1 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] Selection reasons: - By conflict resolution : between versions 2.0.0, 2.0.1 and 1.0 @@ -2982,10 +3014,11 @@ planet:pluto:1.0.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] planet:pluto:1.0.0 diff --git a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportVariantDetailsIntegrationTest.groovy b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportVariantDetailsIntegrationTest.groovy index 945c59e88c47d..694e165c59718 100644 --- a/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportVariantDetailsIntegrationTest.groovy +++ b/subprojects/diagnostics/src/integTest/groovy/org/gradle/api/tasks/diagnostics/DependencyInsightReportVariantDetailsIntegrationTest.groovy @@ -16,6 +16,7 @@ package org.gradle.api.tasks.diagnostics +import org.gradle.api.JavaVersion import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.FeaturePreviewsFixture import org.gradle.integtests.fixtures.resolve.ResolveTestFixture @@ -56,6 +57,8 @@ class DependencyInsightReportVariantDetailsIntegrationTest extends AbstractInteg variant "$expectedVariant" [ $expectedAttributes org.gradle.dependency.bundling = external + org.gradle.category = library (not requested) + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] project :$expectedProject @@ -99,7 +102,7 @@ project :$expectedProject run "dependencyInsight", "--dependency", "leaf" then: - output.contains """org.test:leaf:1.0 + outputContains """org.test:leaf:1.0 variant "api" [ org.gradle.usage = java-api org.gradle.test = published attribute (not requested) @@ -108,6 +111,7 @@ project :$expectedProject Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external org.gradle.blah = something + org.gradle.jvm.version = ${JavaVersion.current().majorVersion} ] org.test:leaf:1.0 @@ -175,9 +179,9 @@ org:middle:1.0 FAILED output.contains """ org:leaf:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:leaf:1.0 @@ -215,11 +219,11 @@ org:leaf:1.0 then: output.contains """org:leaf:1.0 variant "runtime" [ - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: - usage = dummy + usage = dummy ] org:leaf:1.0 @@ -277,10 +281,10 @@ org:leaf:1.0 outputContains """ org:testA:1.0 variant "runtime" [ - custom = dep_value - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + custom = dep_value + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:testA:1.0 @@ -288,10 +292,10 @@ org:testA:1.0 org:testB:1.0 variant "runtime" [ - custom = dep_value - org.gradle.status = release (not requested) - org.gradle.usage = java-runtime (not requested) - org.gradle.component.category = library (not requested) + custom = dep_value + org.gradle.status = release (not requested) + org.gradle.usage = java-runtime (not requested) + org.gradle.category = library (not requested) ] org:testB:+ -> 1.0 diff --git a/subprojects/diagnostics/src/main/java/org/gradle/configuration/TaskDetailPrinter.java b/subprojects/diagnostics/src/main/java/org/gradle/configuration/TaskDetailPrinter.java index 426f2d717ecfa..ac43407dd229e 100644 --- a/subprojects/diagnostics/src/main/java/org/gradle/configuration/TaskDetailPrinter.java +++ b/subprojects/diagnostics/src/main/java/org/gradle/configuration/TaskDetailPrinter.java @@ -17,6 +17,7 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ListMultimap; +import com.google.common.collect.Sets; import org.apache.commons.lang.StringUtils; import org.gradle.api.DefaultTask; import org.gradle.api.Task; @@ -26,16 +27,25 @@ import org.gradle.api.internal.tasks.options.OptionReader; import org.gradle.api.specs.Spec; import org.gradle.execution.TaskSelector; -import org.gradle.internal.logging.text.StyledTextOutput; import org.gradle.internal.logging.text.LinePrefixingStyledTextOutput; +import org.gradle.internal.logging.text.StyledTextOutput; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import static org.gradle.internal.logging.text.StyledTextOutput.Style.UserInput; -import static org.gradle.util.CollectionUtils.sort; import static org.gradle.util.CollectionUtils.collect; import static org.gradle.util.CollectionUtils.filter; -import static org.apache.commons.collections.CollectionUtils.intersection; +import static org.gradle.util.CollectionUtils.sort; public class TaskDetailPrinter { private final String taskPath; @@ -200,7 +210,7 @@ private Map> optionToAvailableValues(List Map> result = new LinkedHashMap>(); for (OptionDescriptor optionDescriptor : allOptions) { if (result.containsKey(optionDescriptor.getName())) { - Collection commonValues = intersection(optionDescriptor.getAvailableValues(), result.get(optionDescriptor.getName())); + Collection commonValues = Sets.intersection(optionDescriptor.getAvailableValues(), result.get(optionDescriptor.getName())); result.put(optionDescriptor.getName(), new TreeSet(commonValues)); } else { result.put(optionDescriptor.getName(), optionDescriptor.getAvailableValues()); diff --git a/subprojects/distributions/binary-compatibility.gradle b/subprojects/distributions/binary-compatibility.gradle index c3f79f0c04679..5587bdab7d726 100644 --- a/subprojects/distributions/binary-compatibility.gradle +++ b/subprojects/distributions/binary-compatibility.gradle @@ -14,9 +14,6 @@ * limitations under the License. */ - - - import japicmp.model.JApiChangeStatus import me.champeau.gradle.japicmp.JapicmpTask import org.gradle.binarycompatibility.AcceptedApiChanges @@ -66,25 +63,23 @@ dependencies { // This transform takes the Gradle zip distribution, // and unzips the Gradle jar files that it contains in a directory - registerTransform { + registerTransform(ExplodeZipAndFindJars) { from.attribute(ARTIFACT_TYPE, 'zip') to.attribute(ARTIFACT_TYPE, 'gradle-libs-dir') - artifactTransform(ExplodeZipAndFindJars) } - registerTransform { + registerTransform(FindGradleClasspath) { from.attribute(ARTIFACT_TYPE, 'gradle-libs-dir') to.attribute(ARTIFACT_TYPE, 'gradle-classpath') - artifactTransform(FindGradleClasspath) } projects.each { projectName -> // This transform uses the result of the exploded zip extraction // and returns a single jar file based on the lookup project name - registerTransform { + registerTransform(FindGradleJar) { from.attribute(ARTIFACT_TYPE, 'gradle-libs-dir') to.attribute(ARTIFACT_TYPE, projectName) - artifactTransform(FindGradleJar) { - params(projectName) + parameters { + target = projectName } } } diff --git a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy index 7538b3f6b359d..2574e2e693512 100644 --- a/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy +++ b/subprojects/distributions/src/integTest/groovy/org/gradle/DistributionIntegrationSpec.groovy @@ -51,7 +51,7 @@ abstract class DistributionIntegrationSpec extends AbstractIntegrationSpec { * Change this if you added or removed dependencies. */ int getThirdPartyLibJarsCount() { - 180 + 177 } int getLibJarsCount() { diff --git a/subprojects/docs/docs.gradle b/subprojects/docs/docs.gradle index 283154d414f0d..7d434336354ee 100755 --- a/subprojects/docs/docs.gradle +++ b/subprojects/docs/docs.gradle @@ -293,7 +293,7 @@ def userguideSinglePage = tasks.register("userguideSinglePage", CacheableAsciido 'source-highlighter': 'coderay' } -def javaApiUrl = "https://docs.oracle.com/en/java/javase/11/docs/api" +def javaApiUrl = "https://docs.oracle.com/javase/8/docs/api" def groovyApiUrl = "http://docs.groovy-lang.org/docs/groovy-${groovyVersion}/html/gapi" def mavenApiUrl = "http://maven.apache.org/ref/${libraries.maven3.version}/maven-model/apidocs" @@ -307,6 +307,7 @@ def javadocAll = tasks.register("javadocAll", Javadoc) { options.charSet = 'utf-8' options.addStringOption 'Xdoclint:syntax,html,reference', '-quiet' options.addStringOption "stylesheetfile", stylesheetFile.absolutePath + options.addStringOption "source", "8" source ProjectGroups.INSTANCE.getPublicJavaProjects(project).collect { project -> project.sourceSets.main.allJava } destinationDir = new File(docsDir, 'javadoc') classpath = configurations.gradleApiRuntime @@ -352,6 +353,7 @@ def distDocs = tasks.register("distDocs", CacheableAsciidoctorTask) { } asciidoctorj { + version = '1.5.8.1' noDefaultRepositories = true } diff --git a/subprojects/docs/src/docs/css/javadoc.css b/subprojects/docs/src/docs/css/javadoc.css index 600f74cc957ba..abe463ecc3383 100644 --- a/subprojects/docs/src/docs/css/javadoc.css +++ b/subprojects/docs/src/docs/css/javadoc.css @@ -1,687 +1,585 @@ -/* This file is a copy of the default stylesheet generated by Java 9 javadoc styles with minor tweaks (fonts and colors). */ +/* This file is a copy of the default stylesheet generated by Java 11 javadoc styles with minor tweaks (fonts and colors). */ +/* + * Javadoc style sheet + */ -/* Lato (normal, regular) */ -@font-face { - font-family: Lato; - font-weight: 400; - font-style: normal; - src: url("https://assets.gradle.com/lato/fonts/lato-normal/lato-normal.woff2") format("woff2"), - url("https://assets.gradle.com/lato/fonts/lato-normal/lato-normal.woff") format("woff"); -} -/* Lato (normal, italic) */ -@font-face { - font-display: swap; - font-family: Lato; - font-weight: 400; - font-style: italic; - src: url("https://assets.gradle.com/lato/fonts/lato-normal-italic/lato-normal-italic.woff2") format("woff2"), - url("https://assets.gradle.com/lato/fonts/lato-normal-italic/lato-normal-italic.woff") format("woff"); -} -/* Lato (bold, regular) */ -@font-face { - font-display: swap; - font-family: Lato; - font-weight: 500; - font-style: normal; - src: url("https://assets.gradle.com/lato/fonts/lato-semibold/lato-semibold.woff2") format("woff2"), - url("https://assets.gradle.com/lato/fonts/lato-semibold/lato-semibold.woff") format("woff"); -} -/* Lato (bold, regular) */ -@font-face { - font-display: swap; - font-family: Lato; - font-weight: 800; - font-style: normal; - src: url("https://assets.gradle.com/lato/fonts/lato-heavy/lato-heavy.woff2") format("woff2"), - url("https://assets.gradle.com/lato/fonts/lato-heavy/lato-heavy.woff") format("woff"); -} +@import url('resources/fonts/dejavu.css'); -/* Javadoc style sheet */ /* -Overall document style -*/ + * Styles for individual HTML elements. + * + * These are styles that are specific to individual HTML elements. Changing them affects the style of a particular + * HTML element throughout the page. + */ + body { - background-color: #ffffff; - color: #353833; - font-family: 'DejaVu Sans', Arial, Helvetica, sans-serif; - font-size: 14px; - margin: 0; - padding: 0; - height: 100%; - width: 100%; + background-color:#ffffff; + color:#353833; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; + margin:0; + padding:0; + height:100%; + width:100%; } - iframe { - margin: 0; - padding: 0; - height: 100%; - width: 100%; - overflow-y: scroll; - border: none; + margin:0; + padding:0; + height:100%; + width:100%; + overflow-y:scroll; + border:none; } - a:link, a:visited { - text-decoration: none; - color: #4A6782; + text-decoration:none; + color:#4A6782; } - a[href]:hover, a[href]:focus { - text-decoration: none; - color: #bb7a2a; + text-decoration:none; + color:#bb7a2a; } - a[name] { - color: #353833; + color:#353833; } - a[name]:before, a[name]:target, a[id]:before, a[id]:target { - content: ""; - display: inline-block; - position: relative; - padding-top: 129px; - margin-top: -129px; -} - -.searchTagResult:before, .searchTagResult:target { - color: red; + content:""; + display:inline-block; + position:relative; + padding-top:129px; + margin-top:-129px; } - pre { - font-family: 'DejaVu Sans Mono', monospace; - font-size: 14px; + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; } - h1 { - font-size: 20px; + font-size:20px; } - h2 { - font-size: 18px; + font-size:18px; } - h3 { - font-size: 16px; - font-style: italic; + font-size:16px; + font-style:italic; } - h4 { - font-size: 13px; + font-size:13px; } - h5 { - font-size: 12px; + font-size:12px; } - h6 { - font-size: 11px; + font-size:11px; } - ul { - list-style-type: disc; + list-style-type:disc; } - code, tt { - font-family: 'DejaVu Sans Mono', monospace; - font-size: 14px; - padding-top: 4px; - margin-top: 8px; - line-height: 1.4em; + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; } - dt code { - font-family: 'DejaVu Sans Mono', monospace; - font-size: 14px; - padding-top: 4px; + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; } - table tr td dt code { - font-family: 'DejaVu Sans Mono', monospace; - font-size: 14px; - vertical-align: top; - padding-top: 4px; + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + vertical-align:top; + padding-top:4px; } - sup { - font-size: 8px; + font-size:8px; } /* -Document title and Copyright styles -*/ + * Styles for HTML generated by javadoc. + * + * These are style classes that are used by the standard doclet to generate HTML documentation. + */ + +/* + * Styles for document title and copyright. + */ .clear { - clear: both; - height: 0px; - overflow: hidden; + clear:both; + height:0px; + overflow:hidden; } - .aboutLanguage { - float: right; - padding: 0px 21px; - font-size: 11px; - z-index: 200; - margin-top: -9px; + float:right; + padding:0px 21px; + font-size:11px; + z-index:200; + margin-top:-9px; } - .legalCopy { - margin-left: .5em; + margin-left:.5em; } - .bar a, .bar a:link, .bar a:visited, .bar a:active { - color: #FFFFFF; - text-decoration: none; + color:#FFFFFF; + text-decoration:none; } - .bar a:hover, .bar a:focus { - color: #bb7a2a; + color:#bb7a2a; } - .tab { - background-color: #0066FF; - color: #ffffff; - padding: 8px; - width: 5em; - font-weight: bold; + background-color:#0066FF; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; } - /* -Navigation bar styles -*/ + * Styles for navigation bar. + */ .bar { - background-color: #DDDDDD; - color: #02303A; - padding: .8em .5em .4em .8em; - height: auto; /*height:1.8em;*/ - font-size: 11px; - margin: 0; + background-color:#4D7A97; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:11px; + margin:0; } - .navPadding { padding-top: 107px; } - +.fixedNav { + position:fixed; + width:100%; + z-index:999; + background-color:#ffffff; +} .topNav { - background-color: #DDDDDD; - color: #02303A; - float: left; - padding: 0; - width: 100%; - clear: right; - height: 2.8em; - padding-top: 10px; - overflow: hidden; - font-size: 12px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; } - .bottomNav { - margin-top: 10px; - background-color: #DDDDDD; - color: #02303A; - float: left; - padding: 0; - width: 100%; - clear: right; - height: 2.8em; - padding-top: 10px; - overflow: hidden; - font-size: 12px; + margin-top:10px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; } - .subNav { - background-color: #dee3e9; - float: left; - width: 100%; - overflow: hidden; - font-size: 12px; + background-color:#dee3e9; + float:left; + width:100%; + overflow:hidden; + font-size:12px; } - .subNav div { - clear: left; - float: left; - padding: 0 0 5px 6px; - text-transform: uppercase; + clear:left; + float:left; + padding:0 0 5px 6px; + text-transform:uppercase; } - ul.navList, ul.subNavList { - float: left; - margin: 0 25px 0 0; - padding: 0; + float:left; + margin:0 25px 0 0; + padding:0; } - -ul.navList li { - list-style: none; - float: left; +ul.navList li{ + list-style:none; + float:left; padding: 5px 6px; - text-transform: uppercase; + text-transform:uppercase; } - ul.navListSearch { - float: right; - margin: 0 0 0 0; - padding: 0; + float:right; + margin:0 0 0 0; + padding:0; } - ul.navListSearch li { - list-style: none; - float: right; + list-style:none; + float:right; padding: 5px 6px; - text-transform: uppercase; + text-transform:uppercase; } - -ul.navListSearch li span { - position: relative; - right: -16px; +ul.navListSearch li label { + position:relative; + right:-16px; } - ul.subNavList li { - list-style: none; - float: left; + list-style:none; + float:left; } - .topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { - color: #FFFFFF; - text-decoration: none; - text-transform: uppercase; + color:#FFFFFF; + text-decoration:none; + text-transform:uppercase; } - .topNav a:hover, .bottomNav a:hover { - text-decoration: none; - color: #bb7a2a; - text-transform: uppercase; + text-decoration:none; + color:#bb7a2a; + text-transform:uppercase; } - .navBarCell1Rev { - background-color: #1BA8CB; - color: #FFFFFF; + background-color:#F8981D; + color:#253441; margin: auto 5px; } - .skipNav { - position: absolute; - top: auto; - left: -9999px; - overflow: hidden; + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; } - /* -Page header and footer styles -*/ + * Styles for page header and footer. + */ .header, .footer { - clear: both; - margin: 0 20px; - padding: 5px 0 0 0; + clear:both; + margin:0 20px; + padding:5px 0 0 0; } - .indexNav { - position: relative; - font-size: 12px; - background-color: #dee3e9; + position:relative; + font-size:12px; + background-color:#dee3e9; } - .indexNav ul { - margin-top: 0; - padding: 5px; + margin-top:0; + padding:5px; } - .indexNav ul li { - display: inline; - list-style-type: none; - padding-right: 10px; - text-transform: uppercase; + display:inline; + list-style-type:none; + padding-right:10px; + text-transform:uppercase; } - .indexNav h1 { - font-size: 13px; + font-size:13px; } - .title { - color: #2c4557; - margin: 10px 0; + color:#2c4557; + margin:10px 0; } - .subTitle { - margin: 5px 0 0 0; + margin:5px 0 0 0; } - .header ul { - margin: 0 0 15px 0; - padding: 0; + margin:0 0 15px 0; + padding:0; } - .footer ul { - margin: 20px 0 5px 0; + margin:20px 0 5px 0; } - .header ul li, .footer ul li { - list-style: none; - font-size: 13px; + list-style:none; + font-size:13px; } - /* -Heading styles -*/ + * Styles for headings. + */ div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { - background-color: #dee3e9; - border: 1px solid #d0d9e0; - margin: 0 0 6px -8px; - padding: 7px 5px; + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; } - ul.blockList ul.blockList ul.blockList li.blockList h3 { - background-color: #dee3e9; - border: 1px solid #d0d9e0; - margin: 0 0 6px -8px; - padding: 7px 5px; + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; } - ul.blockList ul.blockList li.blockList h3 { - padding: 0; - margin: 15px 0; + padding:0; + margin:15px 0; } - ul.blockList li.blockList h2 { - padding: 0px 0 20px 0; + padding:0px 0 20px 0; } - /* -Page layout container styles -*/ -.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { - clear: both; - padding: 10px 20px; - position: relative; + * Styles for page layout containers. + */ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer, +.allClassesContainer, .allPackagesContainer { + clear:both; + padding:10px 20px; + position:relative; } - .indexContainer { - margin: 10px; - position: relative; - font-size: 12px; + margin:10px; + position:relative; + font-size:12px; } - .indexContainer h2 { - font-size: 13px; - padding: 0 0 3px 0; + font-size:13px; + padding:0 0 3px 0; } - .indexContainer ul { - margin: 0; - padding: 0; + margin:0; + padding:0; } - .indexContainer ul li { - list-style: none; - padding-top: 2px; + list-style:none; + padding-top:2px; } - .contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { - font-size: 12px; - font-weight: bold; - margin: 10px 0 0 0; - color: #4E4E4E; + font-size:12px; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; } - .contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { - margin: 5px 0 10px 0px; - font-size: 14px; - font-family: 'DejaVu Serif', Georgia, "Times New Roman", Times, serif; + margin:5px 0 10px 0px; + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; } - .serializedFormContainer dl.nameValue dt { - margin-left: 1px; - font-size: 1.1em; - display: inline; - font-weight: bold; + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; } - .serializedFormContainer dl.nameValue dd { - margin: 0 0 0 1px; - font-size: 1.1em; - display: inline; + margin:0 0 0 1px; + font-size:1.1em; + display:inline; } - /* -List styles -*/ + * Styles for lists. + */ li.circle { - list-style: circle; + list-style:circle; } - ul.horizontal li { - display: inline; - font-size: 0.9em; + display:inline; + font-size:0.9em; } - ul.inheritance { - margin: 0; - padding: 0; + margin:0; + padding:0; } - ul.inheritance li { - display: inline; - list-style: none; + display:inline; + list-style:none; } - ul.inheritance li ul.inheritance { - margin-left: 15px; - padding-left: 15px; - padding-top: 1px; + margin-left:15px; + padding-left:15px; + padding-top:1px; } - ul.blockList, ul.blockListLast { - margin: 10px 0 10px 0; - padding: 0; + margin:10px 0 10px 0; + padding:0; } - ul.blockList li.blockList, ul.blockListLast li.blockList { - list-style: none; - margin-bottom: 15px; - line-height: 1.4; + list-style:none; + margin-bottom:15px; + line-height:1.4; } - ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { - padding: 0px 20px 5px 10px; - border: 1px solid #ededed; - background-color: #f8f8f8; + padding:0px 20px 5px 10px; + border:1px solid #ededed; + background-color:#f8f8f8; } - ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { - padding: 0 0 5px 8px; - background-color: #ffffff; - border: none; + padding:0 0 5px 8px; + background-color:#ffffff; + border:none; } - ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { - margin-left: 0; - padding-left: 0; - padding-bottom: 15px; - border: none; + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; } - ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { - list-style: none; - border-bottom: none; - padding-bottom: 0; + list-style:none; + border-bottom:none; + padding-bottom:0; } - table tr td dl, table tr td dl dt, table tr td dl dd { - margin-top: 0; - margin-bottom: 1px; + margin-top:0; + margin-bottom:1px; } - /* -Table styles -*/ + * Styles for tables. + */ .overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary, .requiresSummary, .packagesSummary, .providesSummary, .usesSummary { - width: 100%; - border-spacing: 0; - border-left: 1px solid #EEE; - border-right: 1px solid #EEE; - border-bottom: 1px solid #EEE; + width:100%; + border-spacing:0; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; } - -.overviewSummary, .memberSummary, .requiresSummary, .packagesSummary, .providesSummary, .usesSummary { - padding: 0px; +.overviewSummary, .memberSummary, .requiresSummary, .packagesSummary, .providesSummary, .usesSummary { + padding:0px; } - .overviewSummary caption, .memberSummary caption, .typeSummary caption, .useSummary caption, .constantsSummary caption, .deprecatedSummary caption, .requiresSummary caption, .packagesSummary caption, .providesSummary caption, .usesSummary caption { - position: relative; - text-align: left; - background-repeat: no-repeat; - color: #FFFFFF; - font-weight: bold; - clear: none; - overflow: hidden; - padding: 0px; - padding-top: 10px; - padding-left: 1px; - margin: 0px; - white-space: pre; + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#253441; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + padding-top:10px; + padding-left:1px; + margin:0px; + white-space:pre; } - .overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, -.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, -.requiresSummary caption a:link, .packagesSummary caption a:link, providesSummary caption a:link, +.constantsSummary caption a:link, .deprecatedSummary caption a:link, +.requiresSummary caption a:link, .packagesSummary caption a:link, .providesSummary caption a:link, .usesSummary caption a:link, .overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, -.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, -.requiresSummary caption a:hover, .packagesSummary caption a:hover, providesSummary caption a:hover, +.constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.requiresSummary caption a:hover, .packagesSummary caption a:hover, .providesSummary caption a:hover, .usesSummary caption a:hover, .overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, -.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, -.requiresSummary caption a:active, .packagesSummary caption a:active, providesSummary caption a:active, +.constantsSummary caption a:active, .deprecatedSummary caption a:active, +.requiresSummary caption a:active, .packagesSummary caption a:active, .providesSummary caption a:active, .usesSummary caption a:active, .overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, -.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited -.requiresSummary caption a:visited, .packagesSummary caption a:visited, providesSummary caption a:visited, +.constantsSummary caption a:visited, .deprecatedSummary caption a:visited, +.requiresSummary caption a:visited, .packagesSummary caption a:visited, .providesSummary caption a:visited, .usesSummary caption a:visited { - color: #FFFFFF; + color:#FFFFFF; +} +.useSummary caption a:link, .useSummary caption a:hover, .useSummary caption a:active, +.useSummary caption a:visited { + color:#1f389c; } - .overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, .useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span, .requiresSummary caption span, .packagesSummary caption span, .providesSummary caption span, .usesSummary caption span { - white-space: nowrap; - padding-top: 5px; - padding-left: 12px; - padding-right: 12px; - padding-bottom: 7px; - display: inline-block; - float: left; - background-color: #1BA8CB; + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; + float:left; + background-color:#F8981D; border: none; - height: 16px; + height:16px; +} +.memberSummary caption span.activeTableTab span, .packagesSummary caption span.activeTableTab span, +.overviewSummary caption span.activeTableTab span, .typeSummary caption span.activeTableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#F8981D; + height:16px; +} +.memberSummary caption span.tableTab span, .packagesSummary caption span.tableTab span, +.overviewSummary caption span.tableTab span, .typeSummary caption span.tableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#4D7A97; + height:16px; } - -.memberSummary caption span.activeTableTab span, .packagesSummary caption span.activeTableTab span { - white-space: nowrap; - padding-top: 5px; - padding-left: 12px; - padding-right: 12px; - margin-right: 3px; - display: inline-block; - float: left; - background-color: #1BA8CB; - height: 16px; -} - -.memberSummary caption span.tableTab span, .packagesSummary caption span.tableTab span { - white-space: nowrap; - padding-top: 5px; - padding-left: 12px; - padding-right: 12px; - margin-right: 3px; - display: inline-block; - float: left; - background-color: #DDDDDD; - height: 16px; -} - .memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab, -.packagesSummary caption span.tableTab, .packagesSummary caption span.activeTableTab { - padding-top: 0px; - padding-left: 0px; - padding-right: 0px; - background-image: none; - float: none; - display: inline; +.packagesSummary caption span.tableTab, .packagesSummary caption span.activeTableTab, +.overviewSummary caption span.tableTab, .overviewSummary caption span.activeTableTab, +.typeSummary caption span.tableTab, .typeSummary caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + padding-right:0px; + background-image:none; + float:none; + display:inline; } - .overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, .useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd, .requiresSummary .tabEnd, .packagesSummary .tabEnd, .providesSummary .tabEnd, .usesSummary .tabEnd { - display: none; - width: 5px; - position: relative; - float: left; - background-color: #1BA8CB; -} - -.memberSummary .activeTableTab .tabEnd, .packagesSummary .activeTableTab .tabEnd { - display: none; - width: 5px; - margin-right: 3px; - position: relative; - float: left; - background-color: #1BA8CB; + display:none; + width:5px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .activeTableTab .tabEnd, .packagesSummary .activeTableTab .tabEnd, +.overviewSummary .activeTableTab .tabEnd, .typeSummary .activeTableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .tableTab .tabEnd, .packagesSummary .tableTab .tabEnd, +.overviewSummary .tableTab .tabEnd, .typeSummary .tableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + background-color:#4D7A97; + float:left; } - -.memberSummary .tableTab .tabEnd, .packagesSummary .tableTab .tabEnd { - display: none; - width: 5px; - margin-right: 3px; - position: relative; - background-color: #DDDDDD; - float: left; - -} - .rowColor th, .altColor th { - font-weight: normal; + font-weight:normal; } - .overviewSummary td, .memberSummary td, .typeSummary td, .useSummary td, .constantsSummary td, .deprecatedSummary td, .requiresSummary td, .packagesSummary td, .providesSummary td, .usesSummary td { - text-align: left; - padding: 0px 0px 12px 10px; -} - -th.colFirst, th.colSecond, th.colLast, th.colConstructorName, .useSummary th, .constantsSummary th, .packagesSummary th, -td.colFirst, td.colSecond, td.colLast, .useSummary td, .constantsSummary td { - vertical-align: top; - padding-right: 0px; - padding-top: 8px; - padding-bottom: 3px; -} - -th.colFirst, th.colSecond, th.colLast, th.colConstructorName, .constantsSummary th, .packagesSummary th { - background: #dee3e9; - text-align: left; - padding: 8px 3px 3px 7px; + text-align:left; + padding:0px 0px 12px 10px; +} +th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .useSummary th, +.constantsSummary th, .packagesSummary th, td.colFirst, td.colSecond, td.colLast, .useSummary td, +.constantsSummary td { + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; +} +th.colFirst, th.colSecond, th.colLast, th.colConstructorName, th.colDeprecatedItemName, .constantsSummary th, +.packagesSummary th { + background:#dee3e9; + text-align:left; + padding:8px 3px 3px 7px; } - td.colFirst, th.colFirst { - white-space: nowrap; - font-size: 13px; + font-size:13px; } - -td.colSecond, th.colSecond, td.colLast, th.colConstructorName, th.colLast { - font-size: 13px; +td.colSecond, th.colSecond, td.colLast, th.colConstructorName, th.colDeprecatedItemName, th.colLast { + font-size:13px; } - .constantsSummary th, .packagesSummary th { - font-size: 13px; + font-size:13px; } - .providesSummary th.colFirst, .providesSummary th.colLast, .providesSummary td.colFirst, .providesSummary td.colLast { - white-space: normal; - font-size: 13px; + white-space:normal; + font-size:13px; } - .overviewSummary td.colFirst, .overviewSummary th.colFirst, .requiresSummary td.colFirst, .requiresSummary th.colFirst, .packagesSummary td.colFirst, .packagesSummary td.colSecond, .packagesSummary th.colFirst, .packagesSummary th, @@ -689,260 +587,245 @@ td.colSecond, th.colSecond, td.colLast, th.colConstructorName, th.colLast { .providesSummary td.colFirst, .providesSummary th.colFirst, .memberSummary td.colFirst, .memberSummary th.colFirst, .memberSummary td.colSecond, .memberSummary th.colSecond, .memberSummary th.colConstructorName, -.typeSummary td.colFirst { - vertical-align: top; +.typeSummary td.colFirst, .typeSummary th.colFirst { + vertical-align:top; } - .packagesSummary th.colLast, .packagesSummary td.colLast { - white-space: normal; + white-space:normal; } - td.colFirst a:link, td.colFirst a:visited, td.colSecond a:link, td.colSecond a:visited, th.colFirst a:link, th.colFirst a:visited, th.colSecond a:link, th.colSecond a:visited, th.colConstructorName a:link, th.colConstructorName a:visited, -td.colLast a:link, td.colLast a:visited, -.constantValuesContainer td a:link, .constantValuesContainer td a:visited { - font-weight: bold; +th.colDeprecatedItemName a:link, th.colDeprecatedItemName a:visited, +.constantValuesContainer td a:link, .constantValuesContainer td a:visited, +.allClassesContainer td a:link, .allClassesContainer td a:visited, +.allPackagesContainer td a:link, .allPackagesContainer td a:visited { + font-weight:bold; } - .tableSubHeadingColor { - background-color: #EEEEFF; + background-color:#EEEEFF; } - .altColor, .altColor th { - background-color: #FFFFFF; + background-color:#FFFFFF; } - .rowColor, .rowColor th { - background-color: #EEEEEF; + background-color:#EEEEEF; } - /* -Content styles -*/ + * Styles for contents. + */ .description pre { - margin-top: 0; + margin-top:0; } - .deprecatedContent { - margin: 0; - padding: 10px 0; + margin:0; + padding:10px 0; } - .docSummary { - padding: 0; + padding:0; } - ul.blockList ul.blockList ul.blockList li.blockList h3 { - font-style: normal; + font-style:normal; } - div.block { - font-size: 14px; - font-family: 'DejaVu Serif', Georgia, "Times New Roman", Times, serif; + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; } - td.colLast div { - padding-top: 0px; + padding-top:0px; } - td.colLast a { - padding-bottom: 3px; + padding-bottom:3px; } - /* -Formatting effect styles -*/ + * Styles for formatting effect. + */ .sourceLineNo { - color: green; - padding: 0 30px 0 0; + color:green; + padding:0 30px 0 0; } - h1.hidden { - visibility: hidden; - overflow: hidden; - font-size: 10px; + visibility:hidden; + overflow:hidden; + font-size:10px; } - .block { - display: block; - margin: 3px 10px 2px 0px; - color: #474747; + display:block; + margin:3px 10px 2px 0px; + color:#474747; } - .deprecatedLabel, .descfrmTypeLabel, .implementationLabel, .memberNameLabel, .memberNameLink, .moduleLabelInPackage, .moduleLabelInType, .overrideSpecifyLabel, .packageLabelInType, .packageHierarchyLabel, .paramLabel, .returnLabel, .seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink, .searchTagLink { - font-weight: bold; + font-weight:bold; } - .deprecationComment, .emphasizedPhrase, .interfaceName { - font-style: italic; -} - -div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, + font-style:italic; +} +.deprecationBlock { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; + border-style:solid; + border-width:thin; + border-radius:10px; + padding:10px; + margin-bottom:10px; + margin-right:10px; + display:inline-block; +} +div.block div.deprecationComment, div.block div.block span.emphasizedPhrase, div.block div.block span.interfaceName { - font-style: normal; + font-style:normal; } - div.contentContainer ul.blockList li.blockList h2 { - padding-bottom: 0px; + padding-bottom:0px; } - /* -IFRAME specific styles -*/ + * Styles for IFRAME. + */ .mainContainer { - margin: 0 auto; - padding: 0; - height: 100%; - width: 100%; - position: fixed; - top: 0; - left: 0; + margin:0 auto; + padding:0; + height:100%; + width:100%; + position:fixed; + top:0; + left:0; } - .leftContainer { - height: 100%; - position: fixed; - width: 320px; + height:100%; + position:fixed; + width:320px; } - .leftTop { - position: relative; - float: left; - width: 315px; - top: 0; - left: 0; - height: 30%; - border-right: 6px solid #ccc; - border-bottom: 6px solid #ccc; + position:relative; + float:left; + width:315px; + top:0; + left:0; + height:30%; + border-right:6px solid #ccc; + border-bottom:6px solid #ccc; } - .leftBottom { - position: relative; - float: left; - width: 315px; - bottom: 0; - left: 0; - height: 70%; - border-right: 6px solid #ccc; - border-top: 1px solid #000; + position:relative; + float:left; + width:315px; + bottom:0; + left:0; + height:70%; + border-right:6px solid #ccc; + border-top:1px solid #000; } - .rightContainer { - position: absolute; - left: 320px; - top: 0; - bottom: 0; - height: 100%; - right: 0; - border-left: 1px solid #000; + position:absolute; + left:320px; + top:0; + bottom:0; + height:100%; + right:0; + border-left:1px solid #000; } - .rightIframe { - margin: 0; - padding: 0; - height: 100%; - right: 30px; - width: 100%; - overflow: visible; - margin-bottom: 30px; + margin:0; + padding:0; + height:100%; + right:30px; + width:100%; + overflow:visible; + margin-bottom:30px; } - /* -HTML5 specific styles -*/ + * Styles specific to HTML5 elements. + */ main, nav, header, footer, section { - display: block; + display:block; } - +/* + * Styles for javadoc search. + */ .ui-autocomplete-category { - font-weight: bold; - font-size: 15px; - padding: 7px 0 7px 3px; - background-color: #DDDDDD; - color: #02303A; + font-weight:bold; + font-size:15px; + padding:7px 0 7px 3px; + background-color:#4D7A97; + color:#FFFFFF; } - .resultItem { - font-size: 13px; + font-size:13px; } - .ui-autocomplete { - max-height: 85%; - max-width: 65%; - overflow-y: scroll; - overflow-x: scroll; - white-space: nowrap; - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + max-height:85%; + max-width:65%; + overflow-y:scroll; + overflow-x:scroll; + white-space:nowrap; + box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); } - ul.ui-autocomplete { - position: fixed; - z-index: 999999; + position:fixed; + z-index:999999; } - -ul.ui-autocomplete li { - float: left; - clear: both; - width: 100%; +ul.ui-autocomplete li { + float:left; + clear:both; + width:100%; } - .resultHighlight { - font-weight: bold; + font-weight:bold; } - #search { - background-image: url('resources/glass.png'); - background-size: 13px; - background-repeat: no-repeat; - background-position: 2px 3px; - padding-left: 20px; - position: relative; - right: -18px; + background-image:url('resources/glass.png'); + background-size:13px; + background-repeat:no-repeat; + background-position:2px 3px; + padding-left:20px; + position:relative; + right:-18px; } - #reset { - background-color: rgb(255, 255, 255); - border: 0 none; - width: 16px; - height: 17px; - position: relative; - left: -2px; - background-image: url('resources/x.png'); - background-repeat: no-repeat; - background-size: 12px; - background-position: center; + background-color: rgb(255,255,255); + background-image:url('resources/x.png'); + background-position:center; + background-repeat:no-repeat; + background-size:12px; + border:0 none; + width:16px; + height:17px; + position:relative; + left:-4px; + top:-4px; + font-size:0px; } - .watermark { - color: #888; + color:#545454; } - .searchTagDescResult { - font-style: italic; - font-size: 11px; + font-style:italic; + font-size:11px; } - .searchTagHolderResult { - font-style: italic; - font-size: 12px; + font-style:italic; + font-size:12px; +} +.searchTagResult:before, .searchTagResult:target { + color:red; } - .moduleGraph span { - display: none; - position: absolute; + display:none; + position:absolute; } - .moduleGraph:hover span { - display: block; + display:block; margin: -100px 0 0 100px; z-index: 1; } +.methodSignature { + white-space:normal; +} /* * Styles for user-provided tables. @@ -969,69 +852,167 @@ table.striped { margin-top: 10px; margin-bottom: 10px; } - table.borderless > caption, table.plain > caption, table.striped > caption { font-weight: bold; font-size: smaller; } - table.borderless th, table.borderless td, table.plain th, table.plain td, table.striped th, table.striped td { padding: 2px 5px; } - table.borderless, table.borderless > thead > tr > th, table.borderless > tbody > tr > th, table.borderless > tr > th, table.borderless > thead > tr > td, table.borderless > tbody > tr > td, table.borderless > tr > td { border: none; } - table.borderless > thead > tr, table.borderless > tbody > tr, table.borderless > tr { background-color: transparent; } - table.plain { border-collapse: collapse; border: 1px solid black; } - table.plain > thead > tr, table.plain > tbody tr, table.plain > tr { background-color: transparent; } - table.plain > thead > tr > th, table.plain > tbody > tr > th, table.plain > tr > th, table.plain > thead > tr > td, table.plain > tbody > tr > td, table.plain > tr > td { border: 1px solid black; } - table.striped { border-collapse: collapse; border: 1px solid black; } - table.striped > thead { - background-color: #DDD; + background-color: #E3E3E3; +} +table.striped > thead > tr > th, table.striped > thead > tr > td { border: 1px solid black; } - table.striped > tbody > tr:nth-child(even) { background-color: #EEE } - table.striped > tbody > tr:nth-child(odd) { background-color: #FFF } - -table.striped > thead > tr > th, table.striped > tbody > tr > th, -table.striped > tbody > tr > td, table.striped > tbody > tr > td { +table.striped > tbody > tr > th, table.striped > tbody > tr > td { border-left: 1px solid black; border-right: 1px solid black; } - +table.striped > tbody > tr > th { + font-weight: normal; +} /* SECTION 2 - Gradle style overrides */ +/*Overwriting colors */ +.bar { + background-color:#DDDDDD; + color:#02303A; +} +.fixedNav { + background-color:#DDDDDD; +} +.topNav { + background-color:#DDDDDD; + color:#02303A; +} +.bottomNav { + background-color:#DDDDDD; + color:#02303A; +} +.navBarCell1Rev { + background-color:#1BA8CB; + color:#FFFFFF; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption, +.requiresSummary caption, .packagesSummary caption, .providesSummary caption, .usesSummary caption { + color:#FFFFFF; +} +.useSummary caption a:link, .useSummary caption a:hover, .useSummary caption a:active, +.useSummary caption a:visited { + color:#FFFFFF; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span, +.requiresSummary caption span, .packagesSummary caption span, .providesSummary caption span, +.usesSummary caption span { + background-color:#1BA8CB; +} +.memberSummary caption span.activeTableTab span, .packagesSummary caption span.activeTableTab span, +.overviewSummary caption span.activeTableTab span, .typeSummary caption span.activeTableTab span { + background-color:#1BA8CB; +} +.memberSummary caption span.tableTab span, .packagesSummary caption span.tableTab span, +.overviewSummary caption span.tableTab span, .typeSummary caption span.tableTab span { + background-color:#DDDDDD; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd, +.requiresSummary .tabEnd, .packagesSummary .tabEnd, .providesSummary .tabEnd, .usesSummary .tabEnd { + background-color:#1BA8CB; +} +.memberSummary .activeTableTab .tabEnd, .packagesSummary .activeTableTab .tabEnd, +.overviewSummary .activeTableTab .tabEnd, .typeSummary .activeTableTab .tabEnd { + background-color:#1BA8CB; +} +.memberSummary .tableTab .tabEnd, .packagesSummary .tableTab .tabEnd, +.overviewSummary .tableTab .tabEnd, .typeSummary .tableTab .tabEnd { + background-color:#DDDDDD; +} +.ui-autocomplete-category { + background-color:#DDDDDD; + color:#02303A; +} +.watermark { + color:#888; +} +table.striped > thead { + background-color: #DDD; +} + +td.colFirst, th.colFirst { + white-space: nowrap; +} + +/* Lato (normal, regular) */ +@font-face { + font-family: Lato; + font-weight: 400; + font-style: normal; + src: url("https://assets.gradle.com/lato/fonts/lato-normal/lato-normal.woff2") format("woff2"), + url("https://assets.gradle.com/lato/fonts/lato-normal/lato-normal.woff") format("woff"); +} +/* Lato (normal, italic) */ +@font-face { + font-display: swap; + font-family: Lato; + font-weight: 400; + font-style: italic; + src: url("https://assets.gradle.com/lato/fonts/lato-normal-italic/lato-normal-italic.woff2") format("woff2"), + url("https://assets.gradle.com/lato/fonts/lato-normal-italic/lato-normal-italic.woff") format("woff"); +} +/* Lato (bold, regular) */ +@font-face { + font-display: swap; + font-family: Lato; + font-weight: 500; + font-style: normal; + src: url("https://assets.gradle.com/lato/fonts/lato-semibold/lato-semibold.woff2") format("woff2"), + url("https://assets.gradle.com/lato/fonts/lato-semibold/lato-semibold.woff") format("woff"); +} +/* Lato (bold, regular) */ +@font-face { + font-display: swap; + font-family: Lato; + font-weight: 800; + font-style: normal; + src: url("https://assets.gradle.com/lato/fonts/lato-heavy/lato-heavy.woff2") format("woff2"), + url("https://assets.gradle.com/lato/fonts/lato-heavy/lato-heavy.woff") format("woff"); +} + body, body.center { background-image: none; color: #02303A; @@ -1174,6 +1155,9 @@ table a:hover, table a:link:active { } /* end anchors color change */ +.fixedNav { + background-color: white; +} .topNav, .bottomNav { margin: 55px 0 0 0; background-color: white; @@ -1224,3 +1208,15 @@ table a:hover, table a:link:active { position: absolute; width: 1px; } +/* Ensure the anchors line up properly */ +div.details a[name] { + padding-top: 187px; + margin-top: -187px; +} +/* Ensure the search reset button is correctly aligned */ +#reset { + background-size:10px; + width:13px; + height:13px; + top:-5px; +} \ No newline at end of file diff --git a/subprojects/docs/src/docs/design/gradle-module-metadata-specification.md b/subprojects/docs/src/docs/design/gradle-module-metadata-1.0-specification.md similarity index 87% rename from subprojects/docs/src/docs/design/gradle-module-metadata-specification.md rename to subprojects/docs/src/docs/design/gradle-module-metadata-1.0-specification.md index e6e0ecc19d904..2908c522c1e35 100644 --- a/subprojects/docs/src/docs/design/gradle-module-metadata-specification.md +++ b/subprojects/docs/src/docs/design/gradle-module-metadata-1.0-specification.md @@ -1,8 +1,8 @@ -# Gradle module metadata specification +# Gradle module metadata 1.0 specification -_Note: this format is not yet stable and may change at any time. Gradle does not guarantee to offer any long term support for this version of the format. Any version before 1.0 is intentionally assumed not backwards compatible, and the parsers are not required to support pre 1.0 releases._ +Consumption of Gradle metadata is automatic. However publication needs to be enabled explicitly for any Gradle version prior to Gradle 6. -Support for Gradle metadata can be enabled in Gradle settings file (`settings.gradle`): +Publishing Gradle metadata can be enabled in Gradle settings file (`settings.gradle`): ``` enableFeaturePreview("GRADLE_METADATA") @@ -10,7 +10,7 @@ enableFeaturePreview("GRADLE_METADATA") ## Goal -This document describes version 0.4 of the Gradle module metadata file. A module metadata file describes the contents of a _module_, which is the unit of publication for a particular repository format, such as a module in a Maven repository. This is often called a "package" in many repository formats. +This document describes version 1.0 of the Gradle module metadata file. A module metadata file describes the contents of a _module_, which is the unit of publication for a particular repository format, such as a module in a Maven repository. This is often called a "package" in many repository formats. The module metadata file is a JSON file published alongside the existing repository specific metadata files, such as a Maven POM or Ivy descriptor. It adds additional metadata that can be used by Gradle versions and other tooling that understand the format. This allows the rich Gradle model to be mapped to and "tunnelled" through existing repository formats, while continuing to support existing Gradle versions and tooling that does not understand the format. @@ -18,7 +18,7 @@ The module metadata file is intended to be machine generated rather than written The module metadata file is also intended to fully describe the binaries in the module where it is present so that it can replace the existing metadata files. This would allow a Gradle repository format to be added, for example. -In version 0.4, the module metadata file can describe only those modules that contain a single _component_, which is some piece of software such as a library or application. Support for more sophisticated mappings will be added by later versions. +In version 1.0, the module metadata file can describe only those modules that contain a single _component_, which is some piece of software such as a library or application. Support for more sophisticated mappings may be added by later versions. ## Usage in a Maven repository @@ -32,7 +32,7 @@ The file must be encoded using UTF-8. The file must contain a JSON object with the following values: -- `formatVersion`: must be present and the first value of the JSON object. Its value must be `"0.4"` +- `formatVersion`: must be present and the first value of the JSON object. Its value must be `"1.0"` - `component`: optional. Describes the identity of the component contained in the module. - `builtBy`: optional. Describes the producer of this metadata file and the contents of the module. - `variants`: optional. Describes the variants of the component packaged in the module, if any. @@ -85,6 +85,16 @@ This value must contain an array of 0 or more capabilities. Each capability is a #### Standard attributes - `org.gradle.usage` indicates the purpose of the variant. See the `org.gradle.api.attributes.Usage` class for more details. Value must be a string. +- `org.gradle.status` indicates the kind of release: one of `release` or `integration`. +- `org.gradle.category` indicates the type of component (library or platform). This attribute is mostly used to disambiguate Maven POM files derived either as a platform or a library. Value must be a string. +- `org.gradle.dependency.bundling` indicates how dependencies of the variant are bundled. Either externally, embedded or shadowed. See the `org.gradle.api.attributes.Bundling` for more details. Value must be a string. + +#### Java Ecosystem specific attributes + +- `org.gradle.jvm.version` indicated the minimal target JVM version of a library. For example is built for java 8, its minimal target is `8`. If it's a multi-release jar for Java 9, 10 and 11, it's minimal target is `9`. Value must be an integer corresponding to the Java version. + +#### Native ecosystem specific attributes + - `org.gradle.native.debuggable` indicates native binaries that are debuggable. Value must be a boolean. ### `available-at` value @@ -151,7 +161,7 @@ This value must contain an array with zero or more elements. Each element must b ``` { - "formatVersion": "0.4", + "formatVersion": "1.0", "component": { "group": "my.group", "module": "mylib", diff --git a/subprojects/docs/src/docs/dsl/dsl.xml b/subprojects/docs/src/docs/dsl/dsl.xml index 982321f7f0e97..b4701e43cca2c 100644 --- a/subprojects/docs/src/docs/dsl/dsl.xml +++ b/subprojects/docs/src/docs/dsl/dsl.xml @@ -665,6 +665,23 @@
+ Native tool chains model types + Used to configure tool chains for building C++ components. + + Native tool chain types + + + + + + + + + +
org.gradle.nativeplatform.toolchain.Gcc
org.gradle.nativeplatform.toolchain.Clang
org.gradle.nativeplatform.toolchain.VisualCpp
+
+ +
Native software model types Used to configure software components developed with native code. @@ -729,15 +746,6 @@ - - - - - - - - - @@ -782,43 +790,52 @@
org.gradle.nativeplatform.Flavor
org.gradle.nativeplatform.toolchain.Gcc
org.gradle.nativeplatform.toolchain.Clang
org.gradle.nativeplatform.toolchain.VisualCpp
org.gradle.language.assembler.AssemblerSourceSet
+
- Native binary task types - Tasks used to build native binaries. + C++ binary task types + Tasks used to build C++ binaries. - Native component task types + C++ component task types - + - + - + - + - + +
org.gradle.language.cpp.tasks.CppCompile
org.gradle.language.c.tasks.CCompileorg.gradle.nativeplatform.tasks.LinkExecutable
org.gradle.language.assembler.tasks.Assembleorg.gradle.nativeplatform.tasks.LinkSharedLibrary
org.gradle.language.objectivec.tasks.ObjectiveCCompileorg.gradle.nativeplatform.tasks.CreateStaticLibrary
org.gradle.language.objectivecpp.tasks.ObjectiveCppCompileorg.gradle.nativeplatform.tasks.InstallExecutable
org.gradle.language.rc.tasks.WindowsResourceCompileorg.gradle.nativeplatform.test.tasks.RunTestExecutable
+
+ +
+ Native binary task types + Tasks used to build native binaries. + + Native component task types - + - + - + - + - +
org.gradle.nativeplatform.tasks.LinkExecutableorg.gradle.language.c.tasks.CCompile
org.gradle.nativeplatform.tasks.LinkSharedLibraryorg.gradle.language.assembler.tasks.Assemble
org.gradle.nativeplatform.tasks.CreateStaticLibraryorg.gradle.language.objectivec.tasks.ObjectiveCCompile
org.gradle.nativeplatform.tasks.InstallExecutableorg.gradle.language.objectivecpp.tasks.ObjectiveCppCompile
org.gradle.nativeplatform.test.tasks.RunTestExecutableorg.gradle.language.rc.tasks.WindowsResourceCompile
diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.Project.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.Project.xml index 3f8684f94a9a0..fe5ee1b3622c6 100644 --- a/subprojects/docs/src/docs/dsl/org.gradle.api.Project.xml +++ b/subprojects/docs/src/docs/dsl/org.gradle.api.Project.xml @@ -233,6 +233,9 @@ normalization + + defaultTasks + diff --git a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.xml b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.xml index 5afc70cf551b5..8d854c17bcaa4 100644 --- a/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.xml +++ b/subprojects/docs/src/docs/dsl/org.gradle.api.tasks.bundling.AbstractArchiveTask.xml @@ -26,11 +26,11 @@ archiveAppendix - null + "" appendix - null + "" baseName @@ -58,11 +58,11 @@ archiveClassifier - null + "" classifier - null + "" destinationDir diff --git a/subprojects/docs/src/docs/release/notes-template.md b/subprojects/docs/src/docs/release/notes-template.md index 3ce4d2ff63e43..1045880aa6246 100644 --- a/subprojects/docs/src/docs/release/notes-template.md +++ b/subprojects/docs/src/docs/release/notes-template.md @@ -1,4 +1,4 @@ -The Gradle team is excited to announce Gradle {gradleVersion}. +The Gradle team is excited to announce Gradle @version@. This release features [1](), [2](), ... [n](), and more. @@ -22,11 +22,9 @@ details of 2 ## Upgrade Instructions -Switch your build to use Gradle {gradleVersion} by updating your wrapper properties: +Switch your build to use Gradle @version@ by updating your wrapper: -`./gradlew wrapper --gradle-version={gradleVersion}` - -Standalone downloads are available at [gradle.org/releases](https://gradle.org/releases). +`./gradlew wrapper --gradle-version=@version@` ## Promoted features Promoted features are features that were incubating in previous versions of Gradle but are now supported and subject to backwards compatibility. @@ -59,7 +57,7 @@ The following are the newly deprecated items in this Gradle release. If you have -See the [Gradle 5.x upgrade guide](userguide/upgrading_version_5.html#changes_{gradleVersion}) to learn about breaking changes and considerations when upgrading to Gradle {gradleVersion}. +See the [Gradle 5.x upgrade guide](userguide/upgrading_version_5.html#changes_@baseVersion@) to learn about breaking changes and considerations when upgrading to Gradle @version@. diff --git a/subprojects/docs/src/docs/release/notes.md b/subprojects/docs/src/docs/release/notes.md index c40a7869e6eb9..1045880aa6246 100644 --- a/subprojects/docs/src/docs/release/notes.md +++ b/subprojects/docs/src/docs/release/notes.md @@ -1,4 +1,4 @@ -The Gradle team is excited to announce Gradle {gradleVersion}. +The Gradle team is excited to announce Gradle @version@. This release features [1](), [2](), ... [n](), and more. @@ -7,7 +7,6 @@ We would like to thank the following community contributors to this release of G Include only their name, impactful features should be called out separately below. [Some person](https://github.com/some-person) --> -[Thad House](https://github.com/ThadHouse) -See the [Gradle 5.x upgrade guide](userguide/upgrading_version_5.html#changes_{gradleVersion}) to learn about breaking changes and considerations when upgrading to Gradle {gradleVersion}. +See the [Gradle 5.x upgrade guide](userguide/upgrading_version_5.html#changes_@baseVersion@) to learn about breaking changes and considerations when upgrading to Gradle @version@. diff --git a/subprojects/docs/src/docs/userguide/command_line_interface.adoc b/subprojects/docs/src/docs/userguide/command_line_interface.adoc index b671cfd4ad054..4ee5cd8facb2d 100644 --- a/subprojects/docs/src/docs/userguide/command_line_interface.adoc +++ b/subprojects/docs/src/docs/userguide/command_line_interface.adoc @@ -61,6 +61,8 @@ $ gradle myTask You can learn about what projects and tasks are available in the <<#sec:command_line_project_reporting, project reporting section>>. +Most builds support a common set of tasks known as <>. These include the `build`, `assemble`, and `check` tasks. + === Executing tasks in multi-project builds In a <>, subproject tasks can be executed with ":" separating subproject name and task name. The following are equivalent _when run from the root project_. diff --git a/subprojects/docs/src/docs/userguide/feature_variants.adoc b/subprojects/docs/src/docs/userguide/feature_variants.adoc index ccabe151d4c28..7cefe20b15d62 100644 --- a/subprojects/docs/src/docs/userguide/feature_variants.adoc +++ b/subprojects/docs/src/docs/userguide/feature_variants.adoc @@ -13,7 +13,7 @@ // limitations under the License. :maven-optional-deps: https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html[Maven optional dependencies] -:metadata-file-spec: https://github.com/gradle/gradle/blob/master/subprojects/docs/src/docs/design/gradle-module-metadata-specification.md +:metadata-file-spec: https://github.com/gradle/gradle/blob/master/subprojects/docs/src/docs/design/gradle-module-metadata-1.0-specification.md [[feature_variants]] = Feature variants and optional dependencies @@ -134,11 +134,11 @@ include::sample[dir="java-feature-variant/producer-separate-sourceset/kotlin",fi Depending on the metadata file format, publishing feature variants may be lossy: - using POM metadata (Maven), feature variants are published as **optional dependencies** and artifacts of feature variants are published with different _classifiers_ -- using Ivy metadata, feature variants are lost +- using Ivy metadata, feature variants are published as extra configurations, which are _not_ extended by the `default` configuration - using {metadata-file-spec}[experimental Gradle metadata], everything is published and consumers will get the full benefit of feature variants ==== -Publishing feature variants is supported using the `maven-publish` plugin only. +Publishing feature variants is supported using the `maven-publish` and `ivy-publish` plugins only. The Java Plugin (or Java Library Plugin) will take care of registering the additional variants for you, so there's no additional configuration required, only the regular publications: .Publishing a component with feature variants @@ -153,10 +153,11 @@ include::sample[dir="java-feature-variant/producer/kotlin",files="build.gradle.k [WARNING] ==== As mentioned earlier, feature variants can be lossy when published. -As a consequence, a consumer can depend on a feature variant only in two cases: +As a consequence, a consumer can depend on a feature variant only in these cases: - with a project dependency (in a multi-project build) -- with Gradle metadata enabled (the publisher MUST have published using Gradle metadata) +- with Gradle metadata available, that is the publisher MUST have published it +- within the Ivy world, by declaring a dependency on the configuration matching the feature ==== A consumer can specify that it needs a specific feature of a producer by declaring required capabilities. diff --git a/subprojects/docs/src/docs/userguide/installation.adoc b/subprojects/docs/src/docs/userguide/installation.adoc index fb3b16e0680c1..04bda07ee6b7d 100644 --- a/subprojects/docs/src/docs/userguide/installation.adoc +++ b/subprojects/docs/src/docs/userguide/installation.adoc @@ -16,7 +16,7 @@ = Installing Gradle You can install the Gradle build tool on Linux, macOS, or Windows. -This document covers installing using a package manager like SDKMAN!, Homebrew, or Scoop, as well as manual installation. +This document covers installing using a package manager like SDKMAN! or Homebrew, as well as manual installation. Use of the <> is the recommended way to upgrade Gradle. @@ -39,7 +39,7 @@ Gradle uses whatever JDK it finds in your path. Alternatively, you can set the ` == Installing with a package manager -link:http://sdkman.io[SDKMAN!] is a tool for managing parallel versions of multiple Software Development Kits on most Unix-based systems. +link:http://sdkman.io[SDKMAN!] is a tool for managing parallel versions of multiple Software Development Kits on most Unix-like systems (macOS, Linux, Cygwin, Solaris and FreeBSD). We deploy and maintain the versions available from SDKMAN!. ---- ❯ sdk install gradle @@ -51,23 +51,7 @@ link:http://brew.sh[Homebrew] is "the missing package manager for macOS". ❯ brew install gradle ---- -link:http://scoop.sh[Scoop] is a command-line installer for Windows inspired by Homebrew. - ----- -❯ scoop install gradle ----- - -link:https://chocolatey.org[Chocolatey] is "the package manager for Windows". - ----- -❯ choco install gradle ----- - -link:https://www.macports.org[MacPorts] is a system for managing tools on macOS: - ----- -❯ sudo port install gradle ----- +Other package managers are available, but the version of Gradle distributed by them is not controlled by Gradle, Inc. Linux package managers may distribute a modified version of Gradle that is incompatible or incomplete when compared to the official version (available from SDKMAN! or below). <<#sec:installation_next_steps,↓ Proceed to next steps>> @@ -140,13 +124,8 @@ Open a console (or a Windows command prompt) and run `gradle -v` to run gradle a Gradle {gradleVersion} ------------------------------------------------------------ -Build time: 2018-02-21 15:28:42 UTC -Revision: 819e0059da49f469d3e9b2896dc4e72537c4847d +(environment specific information) -Groovy: 2.4.15 -Ant: Apache Ant(TM) version 1.9.9 compiled on February 2 2017 -JVM: 1.8.0_151 (Oracle Corporation 25.151-b12) -OS: Mac OS X 10.13.3 x86_64 ---- If you run into any trouble, see the <>. diff --git a/subprojects/docs/src/docs/userguide/java_library_plugin.adoc b/subprojects/docs/src/docs/userguide/java_library_plugin.adoc index b97a17ac3fd1b..c347120ee5610 100644 --- a/subprojects/docs/src/docs/userguide/java_library_plugin.adoc +++ b/subprojects/docs/src/docs/userguide/java_library_plugin.adoc @@ -97,11 +97,11 @@ The following class makes use of a couple of third-party libraries, one of which include::{samplesPath}/java-library/quickstart/groovy/src/main/java/org/gradle/HttpClientWrapper.java[tag=sample] ---- -The _public_ constructor of `HttpClientWrapper` uses `HttpClient` as a parameter, so it is exposed to consumers and therefore belongs to the API. Note that `GetMethod` is used in the signature of a _private_ method, and so it doesn't count towards making HttpClient an API dependency. +The _public_ constructor of `HttpClientWrapper` uses `HttpClient` as a parameter, so it is exposed to consumers and therefore belongs to the API. Note that `HttpGet` and `HttpEntity` are used in the signature of a _private_ method, and so they don't count towards making HttpClient an API dependency. On the other hand, the `ExceptionUtils` type, coming from the `commons-lang` library, is only used in a method body (not in its signature), so it's an implementation dependency. -Therefore, we can deduce that `commons-httpclient` is an API dependency, whereas `commons-lang` is an implementation dependency. This conclusion translates into the following declaration in the build script: +Therefore, we can deduce that `httpclient` is an API dependency, whereas `commons-lang` is an implementation dependency. This conclusion translates into the following declaration in the build script: .Declaring API and implementation dependencies ==== @@ -237,15 +237,14 @@ The role of each configuration is described in the following tables: [[sec:java_library_known_issues_compat]] === Compatibility with other plugins -At the moment the Java Library plugin is only wired to behave correctly with the `java` plugin. Other plugins, such as the Groovy plugin, may not behave correctly. In particular, if the Groovy plugin is used in addition to the `java-library` plugin, then consumers may not get the Groovy classes when they consume the library. To workaround this, you need to explicitly wire the Groovy compile dependency, like this: +At the moment, the Java Library plugin is wired to behave correctly with the `java`, `groovy` and `kotlin` plugins. In Gradle 5.3 or earlier, some plugins, such as the Groovy plugin, may not behave correctly. In particular, if the Groovy plugin is used in addition to the `java-library` plugin, then consumers may not get the Groovy classes when they consume the library. To workaround this, you need to explicitly wire the Groovy compile dependency, like this: -.Configuring the Groovy plugin to work with Java Library +.Configuring the Groovy plugin to work with Java Library in Gradle 5.3 or earlier ==== include::sample[dir="java-library/with-groovy/groovy",files="a/build.gradle[tags=configure-groovy]"] include::sample[dir="java-library/with-groovy/kotlin",files="a/build.gradle.kts[tags=configure-groovy]"] ==== - [[sec:java_library_known_issues_memory]] === Increased memory usage for consumers diff --git a/subprojects/docs/src/docs/userguide/java_platform_plugin.adoc b/subprojects/docs/src/docs/userguide/java_platform_plugin.adoc index 360e06777028e..9e6609bb2ed09 100644 --- a/subprojects/docs/src/docs/userguide/java_platform_plugin.adoc +++ b/subprojects/docs/src/docs/userguide/java_platform_plugin.adoc @@ -30,6 +30,11 @@ it is only used to reference other libraries, so that they play well together du Platforms can be published as {maven-bom}[Maven BOMs] or with the experimental {metadata-file-spec}[Gradle metadata file] format. +[NOTE] +==== +The `java-platform` plugin cannot be used in combination with the `java` or `java-library` plugins in a given project. +Conceptually a project is either a platform, with no binaries, _or_ produces binaries. +==== [[sec:java_platform_usage]] == Usage diff --git a/subprojects/docs/src/docs/userguide/java_plugin.adoc b/subprojects/docs/src/docs/userguide/java_plugin.adoc index fbe79b11cdc68..b15039b40939f 100644 --- a/subprojects/docs/src/docs/userguide/java_plugin.adoc +++ b/subprojects/docs/src/docs/userguide/java_plugin.adoc @@ -28,6 +28,125 @@ include::sample[dir="java/quickstart/groovy", files="build.gradle[tags=use-plugi include::sample[dir="java/quickstart/kotlin", files="build.gradle.kts[tags=use-plugin]"] ==== +[[sec:java_tasks]] +== Tasks + +The Java plugin adds a number of tasks to your project, as shown below. + +`compileJava` — link:{groovyDslPath}/org.gradle.api.tasks.compile.JavaCompile.html[JavaCompile]:: +_Depends on_: All tasks which contribute to the compilation classpath, including `jar` tasks from projects that are on the classpath via project dependencies ++ +Compiles production Java source files using the JDK compiler. + +`processResources` — link:{groovyDslPath}/org.gradle.api.tasks.Copy.html[Copy]:: +Copies production resources into the production resources directory. + +`classes`:: +_Depends on_: `compileJava`, `processResources` ++ +This is an aggregate task that just depends on other tasks. Other plugins may attach additional compilation tasks to it. + +`compileTestJava` — link:{groovyDslPath}/org.gradle.api.tasks.compile.JavaCompile.html[JavaCompile]:: +_Depends on_: `classes`, and all tasks that contribute to the test compilation classpath ++ +Compiles test Java source files using the JDK compiler. + +`processTestResources` — link:{groovyDslPath}/org.gradle.api.tasks.Copy.html[Copy]:: +Copies test resources into the test resources directory. + +`testClasses`:: +_Depends on_: `compileTestJava`, `processTestResources` ++ +This is an aggregate task that just depends on other tasks. Other plugins may attach additional test compilation tasks to it. + +`jar` — link:{groovyDslPath}/org.gradle.api.tasks.bundling.Jar.html[Jar]:: +_Depends on_: `classes` ++ +Assembles the production JAR file, based on the classes and resources attached to the `main` source set. + +`javadoc` — link:{groovyDslPath}/org.gradle.api.tasks.javadoc.Javadoc.html[Javadoc]:: +_Depends on_: `classes` ++ +Generates API documentation for the production Java source using Javadoc. + +`test` — link:{groovyDslPath}/org.gradle.api.tasks.testing.Test.html[Test]:: +_Depends on_: `testClasses`, and all tasks which produce the test runtime classpath ++ +Runs the unit tests using JUnit or TestNG. + +`uploadArchives` — link:{groovyDslPath}/org.gradle.api.tasks.Upload.html[Upload]:: +_Depends on_: `jar`, and any other task that produces an artifact attached to the `archives` configuration ++ +Uploads artifacts in the `archives` configuration — including the production JAR file — to the configured repositories. + +`clean` — link:{groovyDslPath}/org.gradle.api.tasks.Delete.html[Delete]:: +Deletes the project build directory. + +`clean__TaskName__` — link:{groovyDslPath}/org.gradle.api.tasks.Delete.html[Delete]:: +Deletes files created by the specified task. For example, `cleanJar` will delete the JAR file created by the `jar` task and `cleanTest` will delete the test results created by the `test` task. + +[[java_source_set_tasks]] +=== SourceSet Tasks + +For each source set you add to the project, the Java plugin adds the following tasks: + + +`compile__SourceSet__Java` — link:{groovyDslPath}/org.gradle.api.tasks.compile.JavaCompile.html[JavaCompile]:: +_Depends on_: All tasks which contribute to the source set's compilation classpath ++ +Compiles the given source set's Java source files using the JDK compiler. + +`process__SourceSet__Resources` — link:{groovyDslPath}/org.gradle.api.tasks.Copy.html[Copy]:: +Copies the given source set's resources into the resources directory. + +`__sourceSet__Classes` — link:{groovyDslPath}/org.gradle.api.Task.html[Task]:: +_Depends on_: `compile__SourceSet__Java`, `process__SourceSet__Resources` ++ +Prepares the given source set's classes and resources for packaging and execution. Some plugins may add additional compilation tasks for the source set. + +=== Lifecycle Tasks + +The Java plugin attaches some of its tasks to the lifecycle tasks defined by the <> — which the Java Plugin applies automatically — and it also adds a few other lifecycle tasks: + +`assemble`:: +_Depends on_: `jar`, and all other tasks that create artifacts attached to the `archives` configuration ++ +Aggregate task that assembles all the archives in the project. This task is added by the Base Plugin. + +`check`:: +_Depends on_: `test` ++ +Aggregate task that performs verification tasks, such as running the tests. Some plugins add their own verification tasks to `check`. You should also attach any custom `Test` tasks to this lifecycle task if you want them to execute for a full build. This task is added by the Base Plugin. + +`build`:: +_Depends on_: `check`, `assemble` ++ +Aggregate tasks that performs a full build of the project. This task is added by the Base Plugin. + +`buildNeeded`:: +_Depends on_: `build`, and `buildNeeded` tasks in all projects that are dependencies in the `testRuntimeClasspath` configuration. ++ +Performs a full build of the project and all projects it depends on. + +`buildDependents`:: +_Depends on_: `build`, and `buildDependents` tasks in all projects that have this project as a dependency in theeir `testRuntimeClasspath` configurations ++ +Performs a full build of the project and all projects which depend upon it. + +`build__ConfigName__` — _task rule_:: +_Depends on_: all tasks that generate the artifacts attached to the named — _ConfigName_ — configuration ++ +Assembles the artifacts for the specified configuration. This rule is added by the Base Plugin. + +`upload__ConfigName__` — _task rule_, type: link:{groovyDslPath}/org.gradle.api.tasks.Upload.html[Upload]:: +_Depends on_: all tasks that generate the artifacts attached to the named — _ConfigName_ — configuration ++ +Assembles and uploads the artifacts in the specified configuration. This rule is added by the Base Plugin. + +The following diagram shows the relationships between these tasks. + +.Java plugin - tasks +image::javaPluginTasks.png[] [[sec:java_project_layout]] == Project layout @@ -41,6 +160,8 @@ include::javaProjectTestLayout.adoc[] include::javaProjectGenericLayout.adoc[] +[[sec:java_convention_properties]] + [[sec:changing_java_project_layout]] === Changing the project layout @@ -161,128 +282,6 @@ include::sample[dir="userguide/java/sourceSets/groovy", files="build.gradle[tags include::sample[dir="userguide/java/sourceSets/kotlin", files="build.gradle.kts[tags=test]"] ==== -[[sec:java_tasks]] -== Tasks - -The Java plugin adds a number of tasks to your project, as shown below. - -`compileJava` — link:{groovyDslPath}/org.gradle.api.tasks.compile.JavaCompile.html[JavaCompile]:: -_Depends on_: All tasks which contribute to the compilation classpath, including `jar` tasks from projects that are on the classpath via project dependencies -+ -Compiles production Java source files using the JDK compiler. - -`processResources` — link:{groovyDslPath}/org.gradle.api.tasks.Copy.html[Copy]:: -Copies production resources into the production resources directory. - -`classes`:: -_Depends on_: `compileJava`, `processResources` -+ -This is an aggregate task that just depends on other tasks. Other plugins may attach additional compilation tasks to it. - -`compileTestJava` — link:{groovyDslPath}/org.gradle.api.tasks.compile.JavaCompile.html[JavaCompile]:: -_Depends on_: `classes`, and all tasks that contribute to the test compilation classpath -+ -Compiles test Java source files using the JDK compiler. - -`processTestResources` — link:{groovyDslPath}/org.gradle.api.tasks.Copy.html[Copy]:: -Copies test resources into the test resources directory. - -`testClasses`:: -_Depends on_: `compileTestJava`, `processTestResources` -+ -This is an aggregate task that just depends on other tasks. Other plugins may attach additional test compilation tasks to it. - -`jar` — link:{groovyDslPath}/org.gradle.api.tasks.bundling.Jar.html[Jar]:: -_Depends on_: `classes` -+ -Assembles the production JAR file, based on the classes and resources attached to the `main` source set. - -`javadoc` — link:{groovyDslPath}/org.gradle.api.tasks.javadoc.Javadoc.html[Javadoc]:: -_Depends on_: `classes` -+ -Generates API documentation for the production Java source using Javadoc. - -`test` — link:{groovyDslPath}/org.gradle.api.tasks.testing.Test.html[Test]:: -_Depends on_: `testClasses`, and all tasks which produce the test runtime classpath -+ -Runs the unit tests using JUnit or TestNG. - -`uploadArchives` — link:{groovyDslPath}/org.gradle.api.tasks.Upload.html[Upload]:: -_Depends on_: `jar`, and any other task that produces an artifact attached to the `archives` configuration -+ -Uploads artifacts in the `archives` configuration — including the production JAR file — to the configured repositories. - -`clean` — link:{groovyDslPath}/org.gradle.api.tasks.Delete.html[Delete]:: -Deletes the project build directory. - -`clean__TaskName__` — link:{groovyDslPath}/org.gradle.api.tasks.Delete.html[Delete]:: -Deletes files created by the specified task. For example, `cleanJar` will delete the JAR file created by the `jar` task and `cleanTest` will delete the test results created by the `test` task. - -[[java_source_set_tasks]] -=== SourceSet Tasks - -For each source set you add to the project, the Java plugin adds the following tasks: - - -`compile__SourceSet__Java` — link:{groovyDslPath}/org.gradle.api.tasks.compile.JavaCompile.html[JavaCompile]:: -_Depends on_: All tasks which contribute to the source set's compilation classpath -+ -Compiles the given source set's Java source files using the JDK compiler. - -`process__SourceSet__Resources` — link:{groovyDslPath}/org.gradle.api.tasks.Copy.html[Copy]:: -Copies the given source set's resources into the resources directory. - -`__sourceSet__Classes` — link:{groovyDslPath}/org.gradle.api.Task.html[Task]:: -_Depends on_: `compile__SourceSet__Java`, `process__SourceSet__Resources` -+ -Prepares the given source set's classes and resources for packaging and execution. Some plugins may add additional compilation tasks for the source set. - -=== Lifecycle Tasks - -The Java plugin attaches some of its tasks to the lifecycle tasks defined by the <> — which the Java Plugin applies automatically — and it also adds a few other lifecycle tasks: - -`assemble`:: -_Depends on_: `jar`, and all other tasks that create artifacts attached to the `archives` configuration -+ -Aggregate task that assembles all the archives in the project. This task is added by the Base Plugin. - -`check`:: -_Depends on_: `test` -+ -Aggregate task that performs verification tasks, such as running the tests. Some plugins add their own verification tasks to `check`. You should also attach any custom `Test` tasks to this lifecycle task if you want them to execute for a full build. This task is added by the Base Plugin. - -`build`:: -_Depends on_: `check`, `assemble` -+ -Aggregate tasks that performs a full build of the project. This task is added by the Base Plugin. - -`buildNeeded`:: -_Depends on_: `build`, and `buildNeeded` tasks in all projects that are dependencies in the `testRuntimeClasspath` configuration. -+ -Performs a full build of the project and all projects it depends on. - -`buildDependents`:: -_Depends on_: `build`, and `buildDependents` tasks in all projects that have this project as a dependency in theeir `testRuntimeClasspath` configurations -+ -Performs a full build of the project and all projects which depend upon it. - -`build__ConfigName__` — _task rule_:: -_Depends on_: all tasks that generate the artifacts attached to the named — _ConfigName_ — configuration -+ -Assembles the artifacts for the specified configuration. This rule is added by the Base Plugin. - -`upload__ConfigName__` — _task rule_, type: link:{groovyDslPath}/org.gradle.api.tasks.Upload.html[Upload]:: -_Depends on_: all tasks that generate the artifacts attached to the named — _ConfigName_ — configuration -+ -Assembles and uploads the artifacts in the specified configuration. This rule is added by the Base Plugin. - -The following diagram shows the relationships between these tasks. - -.Java plugin - tasks -image::javaPluginTasks.png[] - - - [[sec:java_plugin_and_dependency_management]] == Dependency management @@ -335,16 +334,16 @@ Additional dependencies only for compiling tests, not used at runtime. Test compile classpath, used when compiling test sources. Used by task `compileTestJava`. [.line-through]#`testRuntime`#(Deprecated) extends `runtime, testCompile`:: -Additional dependencies for running tests only. Used by task `test`. Superseded by `testRuntimeOnly`. +Additional dependencies for running tests only. Superseded by `testRuntimeOnly`. `testRuntimeOnly` extends `runtimeOnly`:: -Runtime only dependencies for running tests. Used by task `test`. +Runtime only dependencies for running tests. `testRuntimeClasspath` extends `testRuntimeOnly, testRuntime, testImplementation`:: -Runtime classpath for running tests. +Runtime classpath for running tests. Used by task `test`. `archives`:: -Artifacts (e.g. jars) produced by this project. Used by tasks `uploadArchives`. +Artifacts (e.g. jars) produced by this project. Used by task `uploadArchives`. `default` extends `runtime`:: The default configuration used by a project dependency on this project. Contains the artifacts and dependencies required by this project at runtime. @@ -391,13 +390,6 @@ Runtime only dependencies for the given source set. `__sourceSet__RuntimeClasspath` extends `__sourceSet__RuntimeOnly, __sourceSet__Runtime, __sourceSet__Implementation`:: Runtime classpath contains elements of the implementation, as well as runtime only elements. -[[sec:java_plugin_publishing]] -== Publishing - -`components.java`:: -A link:{javadocPath}/org/gradle/api/component/SoftwareComponent.html[SoftwareComponent] for <> the production JAR created by the `jar` task. This component includes the runtime dependency information for the JAR. - -[[sec:java_convention_properties]] == Convention properties The Java Plugin adds a number of convention properties to the project, shown below. You can use these properties in your build script as though they were properties of the project object. @@ -462,68 +454,19 @@ The manifest to include in all JAR files. Default value: an empty manifest. These properties are provided by convention objects of type link:{javadocPath}/org/gradle/api/plugins/JavaPluginConvention.html[JavaPluginConvention], and link:{javadocPath}/org/gradle/api/plugins/BasePluginConvention.html[BasePluginConvention]. -[[sec:javadoc]] -== Javadoc - -The `javadoc` task is an instance of link:{groovyDslPath}/org.gradle.api.tasks.javadoc.Javadoc.html[Javadoc]. It supports the core Javadoc options and the options of the standard doclet described in the link:{javadocReferenceUrl}[reference documentation] of the Javadoc executable. For a complete list of supported Javadoc options consult the API documentation of the following classes: link:{javadocPath}/org/gradle/external/javadoc/CoreJavadocOptions.html[CoreJavadocOptions] and link:{javadocPath}/org/gradle/external/javadoc/StandardJavadocDocletOptions.html[StandardJavadocDocletOptions]. - -=== Javadoc properties - -`link:{javadocPath}/org/gradle/api/file/FileCollection.html[FileCollection] classpath`:: -Default value: `sourceSets.main.output` + `sourceSets.main.compileClasspath` - -`link:{javadocPath}/org/gradle/api/file/FileTree.html[FileTree] source`:: -Default value: `sourceSets.main.allJava`. Can set using anything described in <>. - -`File destinationDir`:: -Default value: `__docsDir__/javadoc` - -`String title`:: -Default value: The name and version of the project - -[[sec:clean]] -== Clean - -The `clean` task is an instance of link:{groovyDslPath}/org.gradle.api.tasks.Delete.html[Delete]. It simply removes the directory denoted by its `dir` property. - -=== Clean properties - -`File dir`:: -Default value: `__buildDir__` - - -== Resources - -The Java plugin uses the link:{groovyDslPath}/org.gradle.api.tasks.Copy.html[Copy] task for resource handling. It adds an instance for each source set in the project. You can find out more about the copy task in <>. - -=== ProcessResources properties - -`Object srcDirs`:: -Default value: `__sourceSet__.resources`. Can set using anything described in <>. - -`File destinationDir`:: -Default value: `__sourceSet__.output.resourcesDir`. Can set using anything described in <>. - - -== CompileJava - -The Java plugin adds a link:{groovyDslPath}/org.gradle.api.tasks.compile.JavaCompile.html[JavaCompile] instance for each source set in the project. Some of the most common configuration options are shown below. - -=== Compile properties - -`link:{javadocPath}/org/gradle/api/file/FileCollection.html[FileCollection] classpath`:: -Default value: `__sourceSet__.compileClasspath` +[[sec:java_test]] +== Testing -`link:{javadocPath}/org/gradle/api/file/FileTree.html[FileTree] source`:: -Default value: `__sourceSet__.java`. Can set using anything described in <>. +See the <> chapter for more details. -`File destinationDir`:: -Default value: `__sourceSet__.java.outputDir` +[[sec:java_plugin_publishing]] +== Publishing -By default, the Java compiler runs in the Gradle process. Setting `options.fork` to `true` causes compilation to occur in a separate process. In the case of the Ant javac task, this means that a new process will be forked for each compile task, which can slow down compilation. Conversely, Gradle's direct compiler integration (see above) will reuse the same compiler process as much as possible. In both cases, all fork options specified with `options.forkOptions` will be honored. +`components.java`:: +A link:{javadocPath}/org/gradle/api/component/SoftwareComponent.html[SoftwareComponent] for <> the production JAR created by the `jar` task. This component includes the runtime dependency information for the JAR. [[sec:incremental_compile]] -=== Incremental Java compilation +== Incremental Java compilation Gradle comes with a sophisticated incremental Java compiler that is active by default. @@ -548,14 +491,15 @@ To help you understand how incremental compilation works, the following provides * The class analysis is cached in the project directory, so the first build after a clean checkout can be slower. Consider turning off the incremental compiler on your build server. [[sec:incremental_compilation_known_issues]] -==== Known issues +=== Known issues * If a compile task fails due to a compile error, it will do a full compilation again the next time it is invoked. * If you are using an annotation processor that reads resources (e.g. a configuration file), you need to declare those resources as an input of the compile task. * If a resource file is changed, Gradle will trigger a full recompilation. +* If there is a mismatch in the package declaration and the directory structure of source files (e.g. `package foo` vs location `bar/MyClass.java`), then incremental compilation can produce broken output. Wrong classes might be recompiled and there might be leftover class files in the output. [[sec:incremental_annotation_processing]] -=== Incremental annotation processing +== Incremental annotation processing Starting with Gradle 4.7, the incremental compiler also supports incremental annotation processing. All annotation processors need to opt in to this feature, otherwise they will trigger a full recompilation. @@ -563,7 +507,7 @@ All annotation processors need to opt in to this feature, otherwise they will tr As a user you can see which annotation processors are triggering full recompilations in the `--info` log. Incremental annotation processing will be deactivated if a custom `executable` or `javaHome` is configured on the compile task. -==== Making an annotation processor incremental +=== Making an annotation processor incremental Please first have a look at <<#sec:incremental_compile,incremental Java compilation>>, as incremental annotation processing builds on top of it. @@ -601,10 +545,11 @@ Both categories have the following limitations: * They must not depend on compiler-specific APIs like `com.sun.source.util.Trees`. Gradle wraps the processing APIs, so attempts to cast to compiler-specific types will fail. If your processor does this, it cannot be incremental, unless you have some fallback mechanism. -* If they use link:{javaApi}/javax/annotation/processing/Filer.html#createResource(javax.tools.JavaFileManager.Location,java.lang.CharSequence,java.lang.CharSequence,javax.lang.model.element.Element++...++)[Filer#createResource], Gradle will recompile all source files. - See https://github.com/gradle/gradle/issues/4702[gradle/issues/4702] +* If they use link:{javaApi}/javax/annotation/processing/Filer.html#createResource(javax.tools.JavaFileManager.Location,java.lang.CharSequence,java.lang.CharSequence,javax.lang.model.element.Element++...++)[Filer#createResource], + the `location` argument must be one of these values from link:{javaApi}/javax/tools/StandardLocation.html[StandardLocation]: `CLASS_OUTPUT`, `SOURCE_OUTPUT`, or `NATIVE_HEADER_OUTPUT`. + Any other argument will disable incremental processing. -==== "Isolating" annotation processors +=== "Isolating" annotation processors The fastest category, these look at each annotated element in isolation, creating generated files or validation messages for it. For instance an `EntityProcessor` could create a `Repository` for each type annotated with `@Entity`. @@ -631,7 +576,7 @@ include::{samplesPath}/java/incrementalAnnotationProcessing/processor/src/main/j When a source file is recompiled, Gradle will recompile all files generated from it. When a source file is deleted, the files generated from it are deleted. -==== "Aggregating" annotation processors +=== "Aggregating" annotation processors These can aggregate several source files into one ore more output files or validation messages. For instance, a `ServiceRegistryProcessor` could create a single `ServiceRegistry` with one method for each type annotated with `@Service` @@ -652,78 +597,157 @@ include::{samplesPath}/java/incrementalAnnotationProcessing/processor/src/main/j Gradle will always reprocess (but not recompile) all annotated files that the processor was registered for. Gradle will always recompile any files the processor generates. -[[sec:java_compile_avoidance]] -=== Compile avoidance +=== State of support in popular annotation processors -If a dependent project has changed in an https://en.wikipedia.org/wiki/Application_binary_interface[ABI]-compatible way (only its private API has changed), then Java compilation tasks will be up-to-date. This means that if project `A` depends on project `B` and a class in `B` is changed in an ABI-compatible way (typically, changing only the body of a method), then Gradle won't recompile `A`. +[NOTE] +==== +Many popular annotation processors support incremental annotation processing (see the table below). Check with the annotation processor project directly for the most up-to-date information and documentation. +==== -Some of the types of changes that do not affect the public API and are ignored: +[cols="a,a,a", options="header"] +|=== +| Annotation Processor +| Supported since +| Details -* Changing a method body -* Changing a comment -* Adding, removing or changing private methods, fields, or inner classes -* Adding, removing or changing a resource -* Changing the name of jars or directories in the classpath -* Renaming a parameter +| link:https://github.com/google/auto[Auto Value] +| link:https://github.com/google/auto/releases/tag/auto-value-1.6.3[1.6.3] +| N/A +| link:https://github.com/google/auto[Auto Service] +| link:https://github.com/google/auto/releases/tag/auto-value-1.6.3[1.6.3] +| N/A -Since implementation details matter for annotation processors, they must be declared separately on the annotation processor path. -Gradle ignores annotation processors on the compile classpath. +| link:https://github.com/google/auto[Auto Value extensions] +| Partly supported. +| link:https://github.com/google/auto/issues/673[Details in issue] -.Declaring annotation processors -==== -include::sample[dir="java/apt/groovy", files="build.gradle[tags=annotation-processing]"] -include::sample[dir="java/apt/kotlin", files="build.gradle.kts[tags=annotation-processing]"] -==== +| link:https://github.com/JakeWharton/butterknife[Butterknife] +| Unsupported. +| link:https://github.com/stephanenicolas/butterknife[Community fork supports incremental annotation processing] -[[sec:java_test]] -== Test +| link:https://github.com/rzwitserloot/lombok[Lombok] +| link:https://github.com/rzwitserloot/lombok/releases/tag/v1.16.22[1.16.22] +| N/A -The `test` task is an instance of link:{groovyDslPath}/org.gradle.api.tasks.testing.Test.html[Test]. It automatically detects and executes all unit tests in the `test` source set. It also generates a report once test execution is complete. JUnit and TestNG are both supported. Have a look at link:{groovyDslPath}/org.gradle.api.tasks.testing.Test.html[Test] for the complete API. +| DataBinding +| link:https://issuetracker.google.com/issues/110061530#comment28[AGP 3.5.0-alpha5] +| Hidden behind a feature toggle -See the <> chapter for more details. +| Dagger +| link:https://github.com/google/dagger/issues/1120[2.18] +| Hidden behind a feature toggle +| kapt +| link:https://youtrack.jetbrains.com/issue/KT-23880[1.3.30] +| Hidden behind a feature toggle -[[sec:jar]] -== Jar +| Toothpick +| link:https://github.com/stephanenicolas/toothpick/pull/320[2.0] +| N/A -The `jar` task creates a JAR file containing the class files and resources of the project. The JAR file is declared as an artifact in the `archives` dependency configuration. This means that the JAR is available in the classpath of a dependent project. If you upload your project into a repository, this JAR is declared as part of the dependency descriptor. You can learn more about how to work with archives in <> and artifact configurations in <>. +| Glide +| link:https://github.com/bumptech/glide/releases/tag/v4.9.0[4.9.0] +| N/A +| Toothpick +| link:https://github.com/stephanenicolas/toothpick/pull/320[2.0] +| N/A -[[sub:manifest]] -=== Manifest +| Android-State +| link:https://github.com/evernote/android-state/releases/tag/v1.3.0[1.3.0] +| N/A -Each jar or war object has a `manifest` property with a separate instance of link:{javadocPath}/org/gradle/api/java/archives/Manifest.html[Manifest]. When the archive is generated, a corresponding `MANIFEST.MF` file is written into the archive. +| Parceler +| link:https://github.com/johncarl81/parceler/releases/tag/parceler-project-1.1.11[1.1.11] +| N/A -.Customization of MANIFEST.MF -==== -include::sample[dir="userguide/tutorial/manifest/groovy",files="build.gradle[tags=add-to-manifest]"] -include::sample[dir="userguide/tutorial/manifest/kotlin",files="build.gradle.kts[tags=add-to-manifest]"] -==== +| Dart and Henson +| link:https://github.com/f2prateek/dart/releases/tag/3.1.0[3.1.0] +| N/A -You can create stand-alone instances of a `Manifest`. You can use that for example, to share manifest information between jars. +| MapStruct +| link:https://github.com/mapstruct/mapstruct/issues/1420[Open issue] +| N/A -.Creating a manifest object. -==== -include::sample[dir="userguide/tutorial/manifest/groovy",files="build.gradle[tags=custom-manifest]"] -include::sample[dir="userguide/tutorial/manifest/kotlin",files="build.gradle.kts[tags=custom-manifest]"] -==== +| Realm +| link:https://github.com/realm/realm-java/issues/5906[Open issue] +| N/A -You can merge other manifests into any `Manifest` object. The other manifests might be either described by a file path or, like in the example above, by a reference to another `Manifest` object. +| Requery +| link:https://github.com/requery/requery/issues/773[Open issue] +| N/A -.Separate MANIFEST.MF for a particular archive -==== -include::sample[dir="userguide/tutorial/manifest/groovy",files="build.gradle[tags=merge]"] -include::sample[dir="userguide/tutorial/manifest/kotlin",files="build.gradle.kts[tags=merge]"] -==== +| EventBus +| link:https://github.com/greenrobot/EventBus/issues/528[Open issue] +| N/A + +| EclipseLink +| link:https://bugs.eclipse.org/bugs/show_bug.cgi?id=535985[Open issue] +| N/A -Manifests are merged in the order they are declared by the `from` statement. If the base manifest and the merged manifest both define values for the same key, the merged manifest wins by default. You can fully customize the merge behavior by adding `eachEntry` actions in which you have access to a link:{javadocPath}/org/gradle/api/java/archives/ManifestMergeDetails.html[ManifestMergeDetails] instance for each entry of the resulting manifest. The merge is not immediately triggered by the from statement. It is done lazily, either when generating the jar, or by calling `writeTo` or `effectiveManifest` +| PermissionsDispatcher +| link:https://github.com/permissions-dispatcher/PermissionsDispatcher/issues/473[PR merged, waiting for release] +| N/A -You can easily write a manifest to disk. +| Immutables +| link:https://github.com/immutables/immutables/issues/804[Open issue] +| N/A -.Saving a MANIFEST.MF to disk +| link:https://developer.android.com/topic/libraries/architecture/room)[Room] +| link:https://issuetracker.google.com/issues/112110217[Open Issue] +| N/A + +| link:https://developer.android.com/jetpack/androidx/releases/lifecycle)[Lifecycle] +| link:https://issuetracker.google.com/issues/129115778[Open Issue] +| N/A + +| Android Annotations +| link:https://github.com/androidannotations/androidannotations/issues/2193[Open issue] +| N/A + +| DBFlow +| link:https://github.com/agrosner/DBFlow/issues/1648[Open issue] +| N/A + +| AndServer +| link:https://github.com/yanzhenjie/AndServer/issues/152[Open issue] +| N/A + +| Litho +| link:https://github.com/facebook/litho/issues/482[Open issue] +| N/A + +| Moxy +| link:https://github.com/Arello-Mobile/Moxy/issues/240[Open issue] +| N/A + +| Epoxy +| link:https://github.com/airbnb/epoxy/issues/423[Open issue] +| N/A + +|=== + +[[sec:java_compile_avoidance]] +== Compilation avoidance + +If a dependent project has changed in an https://en.wikipedia.org/wiki/Application_binary_interface[ABI]-compatible way (only its private API has changed), then Java compilation tasks will be up-to-date. This means that if project `A` depends on project `B` and a class in `B` is changed in an ABI-compatible way (typically, changing only the body of a method), then Gradle won't recompile `A`. + +Some of the types of changes that do not affect the public API and are ignored: + +* Changing a method body +* Changing a comment +* Adding, removing or changing private methods, fields, or inner classes +* Adding, removing or changing a resource +* Changing the name of jars or directories in the classpath +* Renaming a parameter + + +Since implementation details matter for annotation processors, they must be declared separately on the annotation processor path. +Gradle ignores annotation processors on the compile classpath. + +.Declaring annotation processors ==== -include::sample[dir="userguide/tutorial/manifest/groovy",files="build.gradle[tags=write]"] -include::sample[dir="userguide/tutorial/manifest/kotlin",files="build.gradle.kts[tags=write]"] +include::sample[dir="java/apt/groovy", files="build.gradle[tags=annotation-processing]"] +include::sample[dir="java/apt/kotlin", files="build.gradle.kts[tags=annotation-processing]"] ==== - diff --git a/subprojects/docs/src/docs/userguide/kotlin_dsl.adoc b/subprojects/docs/src/docs/userguide/kotlin_dsl.adoc index 39c9a02e1383f..c5166a4ff7f3e 100644 --- a/subprojects/docs/src/docs/userguide/kotlin_dsl.adoc +++ b/subprojects/docs/src/docs/userguide/kotlin_dsl.adoc @@ -210,8 +210,8 @@ The Kotlin DSL currently supports type-safe model accessors for any of the follo [IMPORTANT] ==== -Only the main project build scripts have type-safe model accessors. -Initialization scripts, settings scripts, script plugins (precompiled or otherwise) do not. +Only the main project build scripts and precompiled project script plugins have type-safe model accessors. +Initialization scripts, settings scripts, script plugins do not. These limitations will be removed in a future Gradle release. ==== @@ -281,10 +281,7 @@ Type-safe accessors are unavailable for model elements contributed by the follow * Script plugins, via `apply(from = "script-plugin.gradle.kts")` * Plugins applied via <> -You also can not use type-safe accessors in: - - * Binary Gradle plugins implemented in Kotlin - * Precompiled script plugins (see below). +You also can not use type-safe accessors in Binary Gradle plugins implemented in Kotlin. If you can't find a type-safe accessor, _fall back to using the normal API_ for the corresponding types. To do that, you need to know the names and/or types of the configured model elements. @@ -755,7 +752,7 @@ So, to apply a precompiled script plugin, you need to know its ID. That is derived from its filename (minus the `.gradle.kts` extension) and its (optional) package declaration. For example, the script `src/main/kotlin/java-library-convention.gradle.kts` would have a plugin ID of `java-library-convention` (assuming it has no package declaration). -Likewise, `src/main/kotlin/my/java-library-convention.gradle.kts` would result in a plugin ID of `my.java-library-convention` as long as it has a package declaration that matches the source directory structure. +Likewise, `src/main/kotlin/my/java-library-convention.gradle.kts` would result in a plugin ID of `my.java-library-convention` as long as it has a package declaration of `my`. To demonstrate how you can implement and use a precompiled script plugin, let's walk through an example based on a `buildSrc` project. @@ -778,12 +775,6 @@ include::sample[dir="kotlinDsl/precompiledScriptPlugins/inBuildSrc",files="build This script plugin simply applies the Java Library and Checkstyle Plugins and configures them. Note that this will actually apply the plugins to the main project, i.e. the one that applies the precompiled script plugin -[IMPORTANT] -==== -Precompiled script plugins do not get <>. -This limitation will be removed in a future Gradle release. -==== - Finally, apply the script plugin to the root project as follows: .Applying the precompiled script plugin to the main project diff --git a/subprojects/docs/src/docs/userguide/migrating_from_maven.adoc b/subprojects/docs/src/docs/userguide/migrating_from_maven.adoc index e6abf3241d1b4..736713e621332 100644 --- a/subprojects/docs/src/docs/userguide/migrating_from_maven.adoc +++ b/subprojects/docs/src/docs/userguide/migrating_from_maven.adoc @@ -41,11 +41,17 @@ This may make migrating between the two seem intimidating, but migrations can be Here we lay out a series of steps for you to follow that will help facilitate the migration of any Maven build to Gradle: - 1. Keep the old Maven build and new Gradle build side by side -+ +TIP: Keep the old Maven build and new Gradle build side by side. You know the Maven build works, so you should keep it until you are confident that the Gradle build produces all the same artifacts and otherwise does what you need. This also means that users can try the Gradle build without getting a new copy of the source tree. - 2. Develop a mechanism to verify that the two builds produce the same artifacts + + . link:https://scans.gradle.com#maven[Create a build scan for the Maven build]. ++ +A build scan will make it easier to visualize what's happening in your existing Maven build. +For Maven builds, you'll be able to see the project structure, what plugins are being used, a timeline of the build steps, and more. +Keep this handy so you can compare it to the Gradle build scans you get while converting the project. ++ +. Develop a mechanism to verify that the two builds produce the same artifacts + This is a vitally important step to ensure that your deployments and tests don't break. Even small changes, such as the contents of a manifest file in a JAR, can cause problems. @@ -58,23 +64,25 @@ You will need to factor in some inherent differences in the build output that Gr Generated POMs will contain only the information needed for consumption and they will use `` and `` scopes correctly for that scenario. You might also see differences in the order of files in archives and of files on classpaths. Most differences will be benign, but it's worth identifying them and verifying that they are OK. - 3. <> + . <> + This will create all the Gradle build files you need, even for <>. For simpler Maven projects, the Gradle build will be ready to run! - 4. link:{guidesUrl}/creating-build-scans/[Create a build scan] + . link:{guidesUrl}/creating-build-scans[Create a build scan for the Gradle build]. + A build scan will make it easier to visualize what's happening in the build. -In particular, you'll be able to see the project structure, the dependencies (regular and inter-project ones), what plugins are being used and the console output of the build. +For Gradle builds, you'll be able to see the project structure, the dependencies (regular and inter-project ones), what plugins are being used and the console output of the build. ++ +Your build may fail at this point, but that's ok; the scan will still run. Compare the build scan for the Gradle build to the one for the Maven build and continue down this list to troubleshoot the failures. + We recommend that you regularly generate build scans during the migration to help you identify and troubleshoot problems. -If you want, you can then use them to identify opportunities to improve the performance of the build, and performance is a big reason for switching to Gradle in the first place. - 5. <> - 6. <> +If you want, you can also use a Gradle build scan to identify opportunities to link:{guidesUrl}/performance/[improve the performance of the build], after all performance is a big reason for switching to Gradle in the first place. + . <> + . <> + Many tests can simply be migrated by configuring an extra source set. If you are using a third-party library, such as http://docs.fitnesse.org/FrontPage[FitNesse], look to see whether there is a suitable community plugin available on the https://plugins.gradle.org/[Gradle Plugin Portal]. - 7. Replace Maven plugins with Gradle equivalents + . Replace Maven plugins with Gradle equivalents + In the case of <>, Gradle often has an equivalent plugin that you can use. You might also find that you can <>. @@ -325,10 +333,10 @@ If you're more interested in controlling which version of a dependency is actual [[migmvn:optional_deps]] === Handling optional dependencies -There are two sides to optional dependencies: +You are likely to encounter two situations regarding optional dependencies: - * How the build treats them as transitive dependencies - * How declared dependencies are published as optional + * Some of your transitive dependencies are declared as optional + * You want to declare some of your direct dependencies as optional in your project's published POM For the first scenario, Gradle behaves the same way as Maven and simply ignores any transitive dependencies that are declared as optional. They are not resolved and have no impact on the versions selected if the same dependencies appear elsewhere in the dependency graph as non-optional. diff --git a/subprojects/docs/src/docs/userguide/more_about_tasks.adoc b/subprojects/docs/src/docs/userguide/more_about_tasks.adoc index 88db2b988e24a..2471cfd7fb5cd 100644 --- a/subprojects/docs/src/docs/userguide/more_about_tasks.adoc +++ b/subprojects/docs/src/docs/userguide/more_about_tasks.adoc @@ -157,7 +157,7 @@ include::sample[dir="userguide/tasks/configureUsingBlock/groovy",files="build.gr include::sample[dir="userguide/tasks/configureUsingBlock/kotlin",files="build.gradle.kts[tags=configure]"] ==== -This works for _any_ task. Task access is just a shortcut for the `tasks.named()` method. It is important to note that if you pass a block to the `named()` method, this block is applied to _configure_ the task, not when the task executes. +This works for _any_ task. Task access is just a shortcut for the `tasks.named()` (Kotlin) or `tasks.getByName()` (Groovy) method. It is important to note that blocks used here are for _configuring_ the task and are not evaluated when the task executes. Have a look at link:{javadocPath}/org/gradle/api/tasks/TaskContainer.html[TaskContainer] for more options for configuring tasks. @@ -643,6 +643,10 @@ Using a file tree turns <> of | Any type | Indicates that the property is used internally but is neither an input nor an output. +| `@link:{javadocPath}/org/gradle/api/model/ReplacedBy.html[ReplacedBy]` +| Any type +| Indicates that the property has been replaced by another and should be ignored as an input or output. + | [#skip-when-empty]`@link:{javadocPath}/org/gradle/api/tasks/SkipWhenEmpty.html[SkipWhenEmpty]` | `File`+++*+++ | Used with `@InputFiles` or `@InputDirectory` to tell Gradle to skip the task if the corresponding files or directory are empty, along with all other input files declared with this annotation. Tasks that have been skipped due to all of their input files that were declared with this annotation being empty will result in a distinct “no source” outcome. For example, `NO-SOURCE` will be emitted in the console output. @@ -1228,9 +1232,9 @@ Lifecycle tasks are tasks that do not do work themselves. They typically do not * a buildable thing (e.g., create a debug 32-bit executable for native components with `debug32MainExecutable`) * a convenience task to execute many of the same logical tasks (e.g., run all compilation tasks with `compileAll`) -Many Gradle plug-ins define their own lifecycle tasks to make it convenient to do specific things. When developing your own plugins, you should consider using your own lifecycle tasks or hooking into some of the tasks already provided by Gradle. See the Java plugin <> for an example. +The Base Plugin defines several <>, such as `build`, `assemble`, and `check`. All the core language plugins, like the <>, apply the Base Plugin and hence have the same base set of lifecycle tasks. -Unless a lifecycle task has actions, its outcome is determined by its dependencies. If any of the task's dependencies are executed, the lifecycle task will be considered executed. If all of the task's dependencies are up-to-date, skipped or from cache, the lifecycle task will be considered up-to-date. +Unless a lifecycle task has actions, its <> is determined by its task dependencies. If any of those dependencies are executed, the lifecycle task will be considered `EXECUTED`. If all of the task dependencies are up to date, skipped or from cache, the lifecycle task will be considered `UP-TO-DATE`. [[sec:the_idea_behind_gradle_tasks]] == Summary diff --git a/subprojects/docs/src/docs/userguide/native_software.adoc b/subprojects/docs/src/docs/userguide/native_software.adoc index c910ad1e43fed..34dbd68a5e832 100644 --- a/subprojects/docs/src/docs/userguide/native_software.adoc +++ b/subprojects/docs/src/docs/userguide/native_software.adoc @@ -16,11 +16,10 @@ = Building native software -[NOTE] +[CAUTION] ==== - -The https://blog.gradle.org/state-and-future-of-the-gradle-software-model[software model] is being retired and the plugins mentioned in this chapter will eventually be deprecated and removed. We recommend new projects looking to build C++ applications and libraries use the newer <>. - +The https://blog.gradle.org/state-and-future-of-the-gradle-software-model[software model] is being retired and the plugins mentioned in this chapter will eventually be deprecated and removed. +We recommend new projects looking to build C++ applications and libraries use the newer <>. ==== The native software plugins add support for building native software components, such as executables or shared libraries, from code written in C++, C and other languages. While many excellent build tools exist for this space of software development, Gradle offers developers its trademark power and flexibility together with dependency management practices more traditionally found in the JVM development space. diff --git a/subprojects/docs/src/docs/userguide/osgi_plugin.adoc b/subprojects/docs/src/docs/userguide/osgi_plugin.adoc index 122e9f7337609..ebcd9831749d2 100644 --- a/subprojects/docs/src/docs/userguide/osgi_plugin.adoc +++ b/subprojects/docs/src/docs/userguide/osgi_plugin.adoc @@ -22,7 +22,7 @@ This plugin is deprecated and will be removed in the next major Gradle release. The OSGi plugin makes heavy use of the http://bnd.bndtools.org/[BND tool]. A separate https://github.com/bndtools/bnd/blob/master/biz.aQute.bnd.gradle/README.md[plugin implementation] is maintained by the BND authors that has more advanced features. ==== -The OSGi plugin provides a factory method to create an link:{javadocPath}/org/gradle/api/plugins/osgi/OsgiManifest.html[OsgiManifest] object. `OsgiManifest` extends link:{javadocPath}/org/gradle/api/java/archives/Manifest.html[Manifest]. To learn more about generic manifest handling, see <>. If the Java plugins is applied, the OSGi plugin replaces the manifest object of the default jar with an `OsgiManifest` object. The replaced manifest is merged into the new one. +The OSGi plugin provides a factory method to create an link:{javadocPath}/org/gradle/api/plugins/osgi/OsgiManifest.html[OsgiManifest] object. `OsgiManifest` extends link:{javadocPath}/org/gradle/api/java/archives/Manifest.html[Manifest]. To learn more about generic manifest handling, see <>. If the Java plugins is applied, the OSGi plugin replaces the manifest object of the default jar with an `OsgiManifest` object. The replaced manifest is merged into the new one. [[sec:osgi_usage]] == Usage diff --git a/subprojects/docs/src/docs/userguide/overview.adoc b/subprojects/docs/src/docs/userguide/overview.adoc deleted file mode 100644 index b0aca30887618..0000000000000 --- a/subprojects/docs/src/docs/userguide/overview.adoc +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2017 the original author or authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -[[overview]] -= Overview - - -[[sec:special_feature_of_gradle]] -== Features - -Here is a list of some of Gradle's features. - -Declarative builds and build-by-convention:: -At the heart of Gradle lies a rich extensible Domain Specific Language (DSL) based on Groovy. Gradle pushes declarative builds to the next level by providing declarative language elements that you can assemble as you like. Those elements also provide build-by-convention support for Java, Groovy, OSGi, Web and Scala projects. Even more, this declarative language is extensible. Add your own new language elements or enhance the existing ones, thus providing concise, maintainable and comprehensible builds. -Language for dependency based programming:: -The declarative language lies on top of a general purpose task graph, which you can fully leverage in your builds. It provides utmost flexibility to adapt Gradle to your unique needs. -Structure your build:: -The suppleness and richness of Gradle finally allows you to apply common design principles to your build. For example, it is very easy to compose your build from reusable pieces of build logic. Inline stuff where unnecessary indirections would be inappropriate. Don't be forced to tear apart what belongs together (e.g. in your project hierarchy). Avoid smells like shotgun changes or divergent change that turn your build into a maintenance nightmare. At last you can create a well structured, easily maintained, comprehensible build. -Deep API:: -From being a pleasure to be used embedded to its many hooks over the whole lifecycle of build execution, Gradle allows you to monitor and customize its configuration and execution behavior to its very core. -Gradle scales:: -Gradle scales very well. It significantly increases your productivity, from simple single project builds up to huge enterprise multi-project builds. This is true for structuring the build. With the state-of-art incremental build function, this is also true for tackling the performance pain many large enterprise builds suffer from. -Multi-project builds:: -Gradle's support for multi-project build is outstanding. Project dependencies are first class citizens. We allow you to model the project relationships in a multi-project build as they really are for your problem domain. Gradle follows your layout not vice versa. -+ -Gradle provides partial builds. If you build a single subproject Gradle takes care of building all the subprojects that subproject depends on. You can also choose to rebuild the subprojects that depend on a particular subproject. Together with incremental builds this is a big time saver for larger builds. -Many ways to manage your dependencies:: -Different teams prefer different ways to manage their external dependencies. Gradle provides convenient support for any strategy. From transitive dependency management with remote Maven and Ivy repositories to jars or directories on the local file system. -Gradle is the first build integration tool:: -Ant tasks are first class citizens. Even more interesting, Ant projects are first class citizens as well. Gradle provides a deep import for any Ant project, turning Ant targets into native Gradle tasks at runtime. You can depend on them from Gradle, you can enhance them from Gradle, you can even declare dependencies on Gradle tasks in your build.xml. The same integration is provided for properties, paths, etc ... -+ -Gradle fully supports your existing Maven or Ivy repository infrastructure for publishing and retrieving dependencies. Gradle also provides a converter for turning a Maven `pom.xml` into a Gradle script. Runtime imports of Maven projects will come soon. -Ease of migration:: -Gradle can adapt to any structure you have. Therefore you can always develop your Gradle build in the same branch where your production build lives and both can evolve in parallel. We usually recommend to write tests that make sure that the produced artifacts are similar. That way migration is as less disruptive and as reliable as possible. This is following the best-practices for refactoring by applying baby steps. -Groovy:: -Gradle's build scripts are written in Groovy or Kotlin, not XML. But unlike other approaches this is not for simply exposing the raw scripting power of a dynamic language. That would just lead to a very difficult to maintain build. The whole design of Gradle is oriented towards being used as a language, not as a rigid framework. And Groovy is our glue that allows you to tell your individual story with the abstractions Gradle (or you) provide. Gradle provides some standard stories but they are not privileged in any form. This is for us a major distinguishing feature compared to other declarative build systems. Our Groovy support is not just sugar coating. The whole Gradle API is fully Groovy-ized. Adding Groovy results in an enjoyable and productive experience. -The Gradle wrapper:: -The Gradle Wrapper allows you to execute Gradle builds on machines where Gradle is not installed. This is useful for example for some continuous integration servers. It is also useful for an open source project to keep the barrier low for building it. The wrapper is also very interesting for the enterprise. It is a zero administration approach for the client machines. It also enforces the usage of a particular Gradle version thus minimizing support issues. -Free and open source:: -Gradle is an open source project, and is licensed under the link:https://github.com/gradle/gradle/blob/master/LICENSE[Apache License 2.0]. - - -[[sec:why_groovy]] -== Why Groovy? - -We think the advantages of an internal DSL (based on a dynamic language) over XML are tremendous when used in _build scripts_. There are a couple of dynamic languages out there. Why Groovy? The answer lies in the context Gradle is operating in. Although Gradle is a general purpose build tool at its core, its main focus are Java projects. In such projects the team members will be very familiar with Java. We think a build should be as transparent as possible to _all_ team members. - -In that case, you might argue why we don't just use Java as the language for build scripts. We think this is a valid question. It would have the highest transparency for your team and the lowest learning curve, but because of the limitations of Java, such a build language would not be as nice, expressive and powerful as it could be.footnote:[At http://www.defmacro.org/ramblings/lisp.html[] you find an interesting article comparing Ant, XML, Java and Lisp. It's funny that the 'if Java had that syntax' syntax in this article is actually the Groovy syntax.] Languages like Python, Groovy or Ruby do a much better job here. We have chosen Groovy as it offers by far the greatest transparency for Java people. Its base syntax is the same as Java's as well as its type system, its package structure and other things. Groovy provides much more on top of that, but with the common foundation of Java. - -For Java developers with Python or Ruby knowledge or the desire to learn them, the above arguments don't apply. The Gradle design is well-suited for creating another build script engine in JRuby or Jython. It just doesn't have the highest priority for us at the moment. We happily support any community effort to create additional build script engines. diff --git a/subprojects/docs/src/docs/userguide/plugins.adoc b/subprojects/docs/src/docs/userguide/plugins.adoc index 9724fcbd3d27b..6b3c0cc2dbb35 100644 --- a/subprojects/docs/src/docs/userguide/plugins.adoc +++ b/subprojects/docs/src/docs/userguide/plugins.adoc @@ -78,7 +78,7 @@ You apply plugins by their _plugin id_, which is a globally unique identifier, o A plugin is simply any class that implements the link:{javadocPath}/org/gradle/api/Plugin.html[Plugin] interface. Gradle provides the core plugins (e.g. `JavaPlugin`) as part of its distribution which means they are automatically resolved. However, non-core binary plugins need to be resolved before they can be applied. This can be achieved in a number of ways: * Including the plugin from the plugin portal or a <<#sec:custom_plugin_repositories,custom repository>> using the plugins DSL (see <<#sec:plugins_block,Applying plugins using the plugins DSL>>). -* Including the plugin from an external jar defined as a buildscript dependency (see see <<#sec:applying_plugins_buildscript,Applying plugins using the buildscript block>>). +* Including the plugin from an external jar defined as a buildscript dependency (see <<#sec:applying_plugins_buildscript,Applying plugins using the buildscript block>>). * Defining the plugin as a source file under the buildSrc directory in the project (see <>). * Defining the plugin as an inline class declaration inside a build script. diff --git a/subprojects/docs/src/docs/userguide/rule_source.adoc b/subprojects/docs/src/docs/userguide/rule_source.adoc index 431010b591654..3583562839980 100644 --- a/subprojects/docs/src/docs/userguide/rule_source.adoc +++ b/subprojects/docs/src/docs/userguide/rule_source.adoc @@ -15,6 +15,13 @@ [[rule_source]] = Implementing model rules in a plugin +[CAUTION] +==== +Rule based configuration link:https://blog.gradle.org/state-and-future-of-the-gradle-software-model[will be deprecated]. +New plugins should not use this concept. +Instead, use the standard approach described in the <> chapter. +==== + A plugin can define rules by extending link:{javadocPath}/org/gradle/model/RuleSource.html[RuleSource] and adding methods that define the rules. The plugin class can either extend link:{javadocPath}/org/gradle/model/RuleSource.html[RuleSource] directly or can implement link:{javadocPath}/org/gradle/api/Plugin.html[Plugin] and include a nested link:{javadocPath}/org/gradle/model/RuleSource.html[RuleSource] subclass. Refer to the API docs for link:{javadocPath}/org/gradle/model/RuleSource.html[RuleSource] for more details. diff --git a/subprojects/docs/src/docs/userguide/signing_plugin.adoc b/subprojects/docs/src/docs/userguide/signing_plugin.adoc index a8a4d7bea91c2..ac11272bdc268 100644 --- a/subprojects/docs/src/docs/userguide/signing_plugin.adoc +++ b/subprojects/docs/src/docs/userguide/signing_plugin.adoc @@ -111,6 +111,19 @@ gradle.taskGraph.whenReady { Note that the presence of a null value for any these three properties will cause an exception. +[[sec:in-memory-keys]] +=== Using in-memory ascii-armored keys + +In some setups it is easier to use environment variables to pass the secret key and password used for signing. +For instance, when using a CI server to sign artifacts, securely providing the keyring file is often troublesome. +On the other hand, most CI servers provide means to securely store environment variables and provide them to builds. +Using the following setup, you can pass the secret key (in ascii-armored format) and the password using the `ORG_GRADLE_PROJECT_signingKey` and `ORG_GRADLE_PROJECT_signingPassword` environment variables, respectively: + +==== +include::sample[dir="signing/in-memory/groovy",files="build.gradle[tags=signing]"] +include::sample[dir="signing/in-memory/kotlin",files="build.gradle.kts[tags=signing]"] +==== + [[sec:subkeys]] === Using OpenPGP subkeys diff --git a/subprojects/docs/src/docs/userguide/software_model.adoc b/subprojects/docs/src/docs/userguide/software_model.adoc index d3384afec18e8..96c26f2fbc6f8 100644 --- a/subprojects/docs/src/docs/userguide/software_model.adoc +++ b/subprojects/docs/src/docs/userguide/software_model.adoc @@ -17,7 +17,9 @@ [CAUTION] ==== -Rule based configuration link:https://blog.gradle.org/state-and-future-of-the-gradle-software-model[will be deprecated]. New plugins should not use this concept. +Rule based configuration link:https://blog.gradle.org/state-and-future-of-the-gradle-software-model[will be deprecated]. +New plugins should not use this concept. +Instead, use the standard approach described in the <> chapter. ==== Rule based model configuration enables _configuration logic to itself have dependencies_ on other elements of configuration, and to make use of the resolved states of those other elements of configuration while performing its own configuration. diff --git a/subprojects/docs/src/docs/userguide/software_model_concepts.adoc b/subprojects/docs/src/docs/userguide/software_model_concepts.adoc index 72f2cdcad9e82..16ae490f17f42 100644 --- a/subprojects/docs/src/docs/userguide/software_model_concepts.adoc +++ b/subprojects/docs/src/docs/userguide/software_model_concepts.adoc @@ -17,7 +17,9 @@ [CAUTION] ==== -Rule based configuration link:https://blog.gradle.org/state-and-future-of-the-gradle-software-model[will be deprecated]. New plugins should not use this concept. +Rule based configuration link:https://blog.gradle.org/state-and-future-of-the-gradle-software-model[will be deprecated]. +New plugins should not use this concept. +Instead, use the standard approach described in the <> chapter. ==== The software model describes how a piece of software is built and how the components of the software relate to each other. The software model is organized around some key concepts: @@ -28,3 +30,5 @@ The software model describes how a piece of software is built and how the compon * A _binary_ represents some output that is built for a component. A component may produce multiple different output binaries. For example, for a C++ library, both a shared library and a static library binary may be produced. Each binary is initially configured to be built from the component sources, but additional source sets can be added to specific binary variants. * A _variant_ represents some mutually exclusive binary of a component. A library, for example, might target Java 7 and Java 8, effectively producing two distinct binaries: a Java 7 Jar and a Java 8 Jar. These are different variants of the library. * The _API_ of a library represents the artifacts and dependencies that are required to compile against that library. The API typically consists of a binary together with a set of dependencies. + +The <>, enabling deep modeling of specific domains via richly typed DSLs. \ No newline at end of file diff --git a/subprojects/docs/src/docs/userguide/software_model_extend.adoc b/subprojects/docs/src/docs/userguide/software_model_extend.adoc index f075ca90fe5df..f8bfa2cd55de2 100644 --- a/subprojects/docs/src/docs/userguide/software_model_extend.adoc +++ b/subprojects/docs/src/docs/userguide/software_model_extend.adoc @@ -17,7 +17,9 @@ [CAUTION] ==== -Rule based configuration link:https://blog.gradle.org/state-and-future-of-the-gradle-software-model[will be deprecated]. New plugins should not use this concept. +Rule based configuration link:https://blog.gradle.org/state-and-future-of-the-gradle-software-model[will be deprecated]. +New plugins should not use this concept. +Instead, use the standard approach described in the <> chapter. ==== == Introduction diff --git a/subprojects/docs/src/docs/userguide/upgrading_version_4.adoc b/subprojects/docs/src/docs/userguide/upgrading_version_4.adoc index 900c372096857..51a7d5891b11b 100644 --- a/subprojects/docs/src/docs/userguide/upgrading_version_4.adoc +++ b/subprojects/docs/src/docs/userguide/upgrading_version_4.adoc @@ -243,7 +243,7 @@ This will lead to some types annotated according to JSR-305 being treated as nul [[deprecations_4.8]] === Deprecations -Prior to this release, builds were allowed to replace built-in tasks. This feature has been deprecated(https://docs.gradle.org/4.8/release-notes.html#overwriting-gradle's-built-in-tasks). +Prior to this release, builds were allowed to replace built-in tasks. link:https://docs.gradle.org/4.8/release-notes.html#overwriting-gradle\'s-built-in-tasks[This feature has been deprecated]. The full list of built-in tasks that should not be replaced is: `wrapper`, `init`, `help`, `tasks`, `projects`, `buildEnvironment`, `components`, `dependencies`, `dependencyInsight`, `dependentComponents`, `model`, `properties`. diff --git a/subprojects/docs/src/docs/userguide/upgrading_version_5.adoc b/subprojects/docs/src/docs/userguide/upgrading_version_5.adoc index a25148fb7cf89..36499ed544afa 100644 --- a/subprojects/docs/src/docs/userguide/upgrading_version_5.adoc +++ b/subprojects/docs/src/docs/userguide/upgrading_version_5.adoc @@ -33,6 +33,77 @@ Some plugins will break with this new version of Gradle, for example because the . Run `gradle wrapper --gradle-version {gradleVersion}` to update the project to {gradleVersion}. . Try to run the project and debug any errors using the <>. +[[changes_5.4]] +== Upgrading from 5.3 and earlier + +=== Deprecated classes, methods and properties + +==== Using custom local build cache implementations + +Using a custom build cache implementation for the local build cache is now deprecated. +The only allowed type will be `DirectoryBuildCache` going forward. +There is no change in the support for using custom build cache implementations as the remote build cache. + +[[changes_5.3]] +== Upgrading from 5.2 and earlier + +=== Potential breaking changes + +==== Bug fixes in platform resolution + +There was a bug from Gradle 5.0 to 5.2.1 (included) where enforced platforms would potentially include dependencies instead of constraints. +This would happen whenever a POM file defined both dependencies and "constraints" (via ``) and that you used `enforcedPlatform`. +Gradle 5.3 fixes this bug, meaning that you might have differences in the resolution result if you relied on this broken behavior. +Similarly, Gradle 5.3 will no longer try to download jars for `platform` and `enforcedPlatform` dependencies (as they should only bring in constraints). + +==== Automatic target JVM version + +If you apply any of the Java plugins, Gradle will now do its best to select dependencies which match the target compatibility of the module being compiled. +What it means, in practice, is that if you have module A built for Java 8, and module B built for Java 8, then there's no change. +However if B is built for Java 9+, then it's not binary compatible anymore, and Gradle would complain with an error message like the following: + +``` +Unable to find a matching variant of project :producer: + - Variant 'apiElements' capability test:producer:unspecified: + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '8' and found incompatible value '9'. + - Required org.gradle.usage 'java-api' and found compatible value 'java-api-jars'. + - Variant 'runtimeElements' capability test:producer:unspecified: + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '8' and found incompatible value '9'. + - Required org.gradle.usage 'java-api' and found compatible value 'java-runtime-jars'. +``` + +In general, this is a sign that your project is misconfigured and that your dependencies are not compatible. +However, there are cases where you still may want to do this, for example when only a _subset_ of classes of your module actually need the Java 9 dependencies, and are not intended to be used on earlier releases. +Java in general doesn't encourage you to do this (you should split your module instead), but if you face this problem, you can workaround by disabling this new behavior on the consumer side: + +``` +java { + disableAutoTargetJvm() +} +``` + +==== Bug fix in Maven / Ivy interoperability with dependency substitution + +If you have a Maven dependency pointing to an Ivy dependency where the `default` configuration dependencies do not match the `compile` + `runtime` + `master` ones +_and_ that Ivy dependency was substituted (using a `resolutionStrategy.force`, `resolutionStrategy.eachDependency` or `resolutionStrategy.dependencySubstitution`) +then this fix will impact you. +The legacy behaviour of Gradle, prior to 5.0, was still in place instead of being replaced by the changes introduced by improved pom support. + +==== Delete operations correctly handle symbolic links on Windows + +Gradle no longer ignores the `followSymlink` option on Windows for the `clean` task, all `Delete` tasks, and `project.delete {}` operations in the presence of junction points and symbolic links. + +==== Fix in publication of additional artifacts + +In previous Gradle versions, additional artifacts registered at the project level were not published by `maven-publish` or `ivy-publish` unless they were also added as artifacts in the publication configuration. + +With Gradle 5.3, these artifacts are now properly accounted for and published. + +This means that artifacts that are registered both on the project _and_ the publication, Ivy or Maven, will cause publication to fail since it will create duplicate entries. +The fix is to remove these artifacts from the publication configuration. + [[changes_5.2]] == Upgrading from 5.1 and earlier @@ -89,7 +160,7 @@ This may break plugins that relied on the previous behaviour. The incubating `operatingSystems` property on native components has been replaced with the link:{javadocPath}/org/gradle/language/cpp/CppComponent.html#getTargetMachines()[targetMachines] property. -### Change in behavior for tasks extending `AbstractArchiveTask` or subtypes (`Zip`, `Jar`, `War`, `Ear`, `Tar`) +### Change in behavior for tasks extending `AbstractArchiveTask` or subtypes (`Zip`, `Jar`, `War`, `Ear`, `Tar`) The `AbstractArchiveTask` has several new properties using the <>. Plugins that extend these types and override methods from the base class may no longer behave the same way. Internally, `AbstractArchiveTask` prefers the new properties and methods like `getArchiveName()` are façades over the new properties. diff --git a/subprojects/docs/src/docs/userguide/userguide_single.adoc b/subprojects/docs/src/docs/userguide/userguide_single.adoc index 5633a35659c59..261a680b1724e 100644 --- a/subprojects/docs/src/docs/userguide/userguide_single.adoc +++ b/subprojects/docs/src/docs/userguide/userguide_single.adoc @@ -28,8 +28,6 @@ toc::[] [[part:about_gradle]] == About Gradle -include::overview.adoc[leveloffset=+2] - include::what_is_gradle.adoc[leveloffset=+2] [[part:getting_started]] diff --git a/subprojects/docs/src/main/resources/header.html b/subprojects/docs/src/main/resources/header.html index f7396deaec1f8..b0b6f9453a09e 100644 --- a/subprojects/docs/src/main/resources/header.html +++ b/subprojects/docs/src/main/resources/header.html @@ -197,11 +197,6 @@

Authoring Gradle Builds

  • diff --git a/subprojects/docs/src/samples/customModel/internalViews/softwareModelExtend-iv-model.out b/subprojects/docs/src/samples/customModel/internalViews/softwareModelExtend-iv-model.out index 9ded9af2c68dd..5c8eb102f1619 100644 --- a/subprojects/docs/src/samples/customModel/internalViews/softwareModelExtend-iv-model.out +++ b/subprojects/docs/src/samples/customModel/internalViews/softwareModelExtend-iv-model.out @@ -93,6 +93,12 @@ Root project | Creator: Project..tasks.model() | Rules: ⤷ copyToTaskContainer + + prepareKotlinBuildScriptModel + | Type: org.gradle.api.DefaultTask + | Value: task ':prepareKotlinBuildScriptModel' + | Creator: Project..tasks.prepareKotlinBuildScriptModel() + | Rules: + ⤷ copyToTaskContainer + projects | Type: org.gradle.api.tasks.diagnostics.ProjectReportTask | Value: task ':projects' diff --git a/subprojects/docs/src/samples/java-feature-variant/incompatible-variants/kotlin/runtimeClasspath.out b/subprojects/docs/src/samples/java-feature-variant/incompatible-variants/kotlin/runtimeClasspath.out index 6f6b0059438e4..5bb8a22385fec 100644 --- a/subprojects/docs/src/samples/java-feature-variant/incompatible-variants/kotlin/runtimeClasspath.out +++ b/subprojects/docs/src/samples/java-feature-variant/incompatible-variants/kotlin/runtimeClasspath.out @@ -4,7 +4,7 @@ mysql:mysql-connector-java:8.0.14 variant "runtime" [ org.gradle.status = release (not requested) org.gradle.usage = java-runtime - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) ] mysql:mysql-connector-java:8.0.14 diff --git a/subprojects/docs/src/samples/java-feature-variant/requiring-features-external/repo/org/gradle/demo/producer/1.0/producer-1.0.module b/subprojects/docs/src/samples/java-feature-variant/requiring-features-external/repo/org/gradle/demo/producer/1.0/producer-1.0.module index c895deabe5806..a6f21573f4e08 100644 --- a/subprojects/docs/src/samples/java-feature-variant/requiring-features-external/repo/org/gradle/demo/producer/1.0/producer-1.0.module +++ b/subprojects/docs/src/samples/java-feature-variant/requiring-features-external/repo/org/gradle/demo/producer/1.0/producer-1.0.module @@ -1,5 +1,5 @@ { - "formatVersion": "0.4", + "formatVersion": "1.0", "component": { "group": "org.gradle.demo", "module": "producer", diff --git a/subprojects/docs/src/samples/java-feature-variant/requiring-features-external/runtimeClasspath.out b/subprojects/docs/src/samples/java-feature-variant/requiring-features-external/runtimeClasspath.out index b20fa74b5cdfd..c80e0159ba2c7 100644 --- a/subprojects/docs/src/samples/java-feature-variant/requiring-features-external/runtimeClasspath.out +++ b/subprojects/docs/src/samples/java-feature-variant/requiring-features-external/runtimeClasspath.out @@ -4,10 +4,11 @@ org.mongodb:bson:3.9.1 variant "runtime" [ org.gradle.status = release (not requested) org.gradle.usage = java-runtime - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = 11 ] org.mongodb:bson:3.9.1 @@ -21,10 +22,11 @@ org.mongodb:mongodb-driver-core:3.9.1 variant "runtime" [ org.gradle.status = release (not requested) org.gradle.usage = java-runtime - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = 11 ] org.mongodb:mongodb-driver-core:3.9.1 @@ -36,10 +38,11 @@ org.mongodb:mongodb-driver-sync:3.9.1 variant "runtime" [ org.gradle.status = release (not requested) org.gradle.usage = java-runtime - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = 11 ] org.mongodb:mongodb-driver-sync:3.9.1 diff --git a/subprojects/docs/src/samples/java-feature-variant/requiring-features/runtimeClasspath.out b/subprojects/docs/src/samples/java-feature-variant/requiring-features/runtimeClasspath.out index 4bd3c76e624ec..bf6ef85c99963 100644 --- a/subprojects/docs/src/samples/java-feature-variant/requiring-features/runtimeClasspath.out +++ b/subprojects/docs/src/samples/java-feature-variant/requiring-features/runtimeClasspath.out @@ -4,10 +4,11 @@ mysql:mysql-connector-java:8.0.14 variant "runtime" [ org.gradle.status = release (not requested) org.gradle.usage = java-runtime - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = 11 ] mysql:mysql-connector-java:8.0.14 diff --git a/subprojects/docs/src/samples/java-library/quickstart/groovy/build.gradle b/subprojects/docs/src/samples/java-library/quickstart/groovy/build.gradle index 83f474fe4e9a6..b076ffcd374b3 100644 --- a/subprojects/docs/src/samples/java-library/quickstart/groovy/build.gradle +++ b/subprojects/docs/src/samples/java-library/quickstart/groovy/build.gradle @@ -29,7 +29,7 @@ repositories { // tag::dependencies[] dependencies { - api 'commons-httpclient:commons-httpclient:3.1' + api 'org.apache.httpcomponents:httpclient:4.5.7' implementation 'org.apache.commons:commons-lang3:3.5' } // end::dependencies[] diff --git a/subprojects/docs/src/samples/java-library/quickstart/groovy/src/main/java/org/gradle/HttpClientWrapper.java b/subprojects/docs/src/samples/java-library/quickstart/groovy/src/main/java/org/gradle/HttpClientWrapper.java index bd442b0315f85..f137bb35b65ec 100644 --- a/subprojects/docs/src/samples/java-library/quickstart/groovy/src/main/java/org/gradle/HttpClientWrapper.java +++ b/subprojects/docs/src/samples/java-library/quickstart/groovy/src/main/java/org/gradle/HttpClientWrapper.java @@ -17,9 +17,14 @@ // tag::sample[] // The following types can appear anywhere in the code // but say nothing about API or implementation usage -import org.apache.commons.httpclient.*; -import org.apache.commons.httpclient.methods.*; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; + +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -35,26 +40,27 @@ public HttpClientWrapper(HttpClient client) { // public methods belongs to your API public byte[] doRawGet(String url) { - GetMethod method = new GetMethod(url); + HttpGet request = new HttpGet(url); try { - int statusCode = doGet(method); - return method.getResponseBody(); - + HttpEntity entity = doGet(request); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + entity.writeTo(baos); + return baos.toByteArray(); } catch (Exception e) { ExceptionUtils.rethrow(e); // this dependency is internal only } finally { - method.releaseConnection(); + request.releaseConnection(); } return null; } - // GetMethod is used in a private method, so doesn't belong to the API - private int doGet(GetMethod method) throws Exception { - int statusCode = client.executeMethod(method); - if (statusCode != HttpStatus.SC_OK) { - System.err.println("Method failed: " + method.getStatusLine()); + // HttpGet and HttpEntity are used in a private method, so they don't belong to the API + private HttpEntity doGet(HttpGet get) throws Exception { + HttpResponse response = client.execute(get); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + System.err.println("Method failed: " + response.getStatusLine()); } - return statusCode; + return response.getEntity(); } } // end::sample[] diff --git a/subprojects/docs/src/samples/java-library/quickstart/kotlin/build.gradle.kts b/subprojects/docs/src/samples/java-library/quickstart/kotlin/build.gradle.kts index 1b0b329147e8b..63e113ae80ac3 100644 --- a/subprojects/docs/src/samples/java-library/quickstart/kotlin/build.gradle.kts +++ b/subprojects/docs/src/samples/java-library/quickstart/kotlin/build.gradle.kts @@ -13,7 +13,7 @@ repositories { // tag::dependencies[] dependencies { - api("commons-httpclient:commons-httpclient:3.1") + api("org.apache.httpcomponents:httpclient:4.5.7") implementation("org.apache.commons:commons-lang3:3.5") } // end::dependencies[] diff --git a/subprojects/docs/src/samples/java-library/quickstart/kotlin/src/main/java/org/gradle/HttpClientWrapper.java b/subprojects/docs/src/samples/java-library/quickstart/kotlin/src/main/java/org/gradle/HttpClientWrapper.java index bd442b0315f85..f137bb35b65ec 100644 --- a/subprojects/docs/src/samples/java-library/quickstart/kotlin/src/main/java/org/gradle/HttpClientWrapper.java +++ b/subprojects/docs/src/samples/java-library/quickstart/kotlin/src/main/java/org/gradle/HttpClientWrapper.java @@ -17,9 +17,14 @@ // tag::sample[] // The following types can appear anywhere in the code // but say nothing about API or implementation usage -import org.apache.commons.httpclient.*; -import org.apache.commons.httpclient.methods.*; import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; + +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -35,26 +40,27 @@ public HttpClientWrapper(HttpClient client) { // public methods belongs to your API public byte[] doRawGet(String url) { - GetMethod method = new GetMethod(url); + HttpGet request = new HttpGet(url); try { - int statusCode = doGet(method); - return method.getResponseBody(); - + HttpEntity entity = doGet(request); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + entity.writeTo(baos); + return baos.toByteArray(); } catch (Exception e) { ExceptionUtils.rethrow(e); // this dependency is internal only } finally { - method.releaseConnection(); + request.releaseConnection(); } return null; } - // GetMethod is used in a private method, so doesn't belong to the API - private int doGet(GetMethod method) throws Exception { - int statusCode = client.executeMethod(method); - if (statusCode != HttpStatus.SC_OK) { - System.err.println("Method failed: " + method.getStatusLine()); + // HttpGet and HttpEntity are used in a private method, so they don't belong to the API + private HttpEntity doGet(HttpGet get) throws Exception { + HttpResponse response = client.execute(get); + if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { + System.err.println("Method failed: " + response.getStatusLine()); } - return statusCode; + return response.getEntity(); } } // end::sample[] diff --git a/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/build.gradle.kts b/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/build.gradle.kts index 2caff31eb61c9..1ebf38ef45435 100644 --- a/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/build.gradle.kts +++ b/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/build.gradle.kts @@ -1,3 +1,3 @@ plugins { - id("java-library-convention") + `java-library-convention` } diff --git a/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/buildSrc/build.gradle.kts b/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/buildSrc/build.gradle.kts index 244e36726277d..230ab933f25be 100644 --- a/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/buildSrc/build.gradle.kts +++ b/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/buildSrc/build.gradle.kts @@ -2,8 +2,8 @@ plugins { `kotlin-dsl` } -// end::apply[] repositories { jcenter() } +// end::apply[] diff --git a/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/buildSrc/src/main/kotlin/java-library-convention.gradle.kts b/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/buildSrc/src/main/kotlin/java-library-convention.gradle.kts index 04dfaca0a39ed..84e5adf9ea360 100644 --- a/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/buildSrc/src/main/kotlin/java-library-convention.gradle.kts +++ b/subprojects/docs/src/samples/kotlinDsl/precompiledScriptPlugins/inBuildSrc/buildSrc/src/main/kotlin/java-library-convention.gradle.kts @@ -3,12 +3,12 @@ plugins { checkstyle } -configure { +java { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } -configure { +checkstyle { maxWarnings = 0 // ... } @@ -19,6 +19,6 @@ tasks.withType { } dependencies { - "testImplementation"("junit:junit:4.12") + testImplementation("junit:junit:4.12") // ... } diff --git a/subprojects/docs/src/samples/modelRules/basicRuleSourcePlugin/basicRuleSourcePlugin-model-task.out b/subprojects/docs/src/samples/modelRules/basicRuleSourcePlugin/basicRuleSourcePlugin-model-task.out index 70b2e7bba6b81..1be43c8d69595 100644 --- a/subprojects/docs/src/samples/modelRules/basicRuleSourcePlugin/basicRuleSourcePlugin-model-task.out +++ b/subprojects/docs/src/samples/modelRules/basicRuleSourcePlugin/basicRuleSourcePlugin-model-task.out @@ -112,6 +112,12 @@ Root project | Creator: Project..tasks.model() | Rules: ⤷ copyToTaskContainer + + prepareKotlinBuildScriptModel + | Type: org.gradle.api.DefaultTask + | Value: task ':prepareKotlinBuildScriptModel' + | Creator: Project..tasks.prepareKotlinBuildScriptModel() + | Rules: + ⤷ copyToTaskContainer + projects | Type: org.gradle.api.tasks.diagnostics.ProjectReportTask | Value: task ':projects' diff --git a/subprojects/docs/src/samples/signing/in-memory/groovy/build.gradle b/subprojects/docs/src/samples/signing/in-memory/groovy/build.gradle new file mode 100644 index 0000000000000..15ee9723fe3ca --- /dev/null +++ b/subprojects/docs/src/samples/signing/in-memory/groovy/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'signing' +} + +task stuffZip(type: Zip) { + archiveBaseName = 'stuff' + from 'src/stuff' +} + +// tag::signing[] +signing { + def signingKey = findProperty("signingKey") + def signingPassword = findProperty("signingPassword") + useInMemoryPgpKeys(signingKey, signingPassword) + sign stuffZip +} +// end::signing[] diff --git a/subprojects/docs/src/samples/signing/in-memory/groovy/settings.gradle b/subprojects/docs/src/samples/signing/in-memory/groovy/settings.gradle new file mode 100644 index 0000000000000..1466302c71b23 --- /dev/null +++ b/subprojects/docs/src/samples/signing/in-memory/groovy/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'in-memory' diff --git a/subprojects/docs/src/samples/signing/in-memory/groovy/src/stuff/hello.txt b/subprojects/docs/src/samples/signing/in-memory/groovy/src/stuff/hello.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/subprojects/docs/src/samples/signing/in-memory/kotlin/build.gradle.kts b/subprojects/docs/src/samples/signing/in-memory/kotlin/build.gradle.kts new file mode 100644 index 0000000000000..5363e20381593 --- /dev/null +++ b/subprojects/docs/src/samples/signing/in-memory/kotlin/build.gradle.kts @@ -0,0 +1,17 @@ +plugins { + signing +} + +tasks.register("stuffZip") { + archiveBaseName.set("stuff") + from("src/stuff") +} + +// tag::signing[] +signing { + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKey, signingPassword) + sign(tasks["stuffZip"]) +} +// end::sign-task[] diff --git a/subprojects/docs/src/samples/signing/in-memory/kotlin/settings.gradle.kts b/subprojects/docs/src/samples/signing/in-memory/kotlin/settings.gradle.kts new file mode 100644 index 0000000000000..32316f96b142c --- /dev/null +++ b/subprojects/docs/src/samples/signing/in-memory/kotlin/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "in-memory" diff --git a/subprojects/docs/src/samples/signing/in-memory/kotlin/src/stuff/hello.txt b/subprojects/docs/src/samples/signing/in-memory/kotlin/src/stuff/hello.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/subprojects/docs/src/samples/userguide/antMigration/multiProject/groovy/web/build.xml b/subprojects/docs/src/samples/userguide/antMigration/multiProject/groovy/web/build.xml index e03ca31506a48..faf25ec6a133d 100644 --- a/subprojects/docs/src/samples/userguide/antMigration/multiProject/groovy/web/build.xml +++ b/subprojects/docs/src/samples/userguide/antMigration/multiProject/groovy/web/build.xml @@ -7,7 +7,7 @@ - + diff --git a/subprojects/docs/src/samples/userguide/dependencyManagement/inspectingDependencies/dependencyReason/dependencyReasonReport.out b/subprojects/docs/src/samples/userguide/dependencyManagement/inspectingDependencies/dependencyReason/dependencyReasonReport.out index e39172615c03f..c1e90fc3e4c30 100644 --- a/subprojects/docs/src/samples/userguide/dependencyManagement/inspectingDependencies/dependencyReason/dependencyReasonReport.out +++ b/subprojects/docs/src/samples/userguide/dependencyManagement/inspectingDependencies/dependencyReason/dependencyReasonReport.out @@ -2,10 +2,11 @@ org.ow2.asm:asm:6.0 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = 11 ] Selection reasons: - Was requested : we require a JDK 9 compatible bytecode generator diff --git a/subprojects/docs/src/samples/userguide/dependencyManagement/managingTransitiveDependencies/declaringCapabilities/dependencyReport.out b/subprojects/docs/src/samples/userguide/dependencyManagement/managingTransitiveDependencies/declaringCapabilities/dependencyReport.out index 53282e4a219c8..baf981f1f7278 100644 --- a/subprojects/docs/src/samples/userguide/dependencyManagement/managingTransitiveDependencies/declaringCapabilities/dependencyReport.out +++ b/subprojects/docs/src/samples/userguide/dependencyManagement/managingTransitiveDependencies/declaringCapabilities/dependencyReport.out @@ -21,10 +21,11 @@ org.slf4j:slf4j-log4j12:1.6.1 variant "compile" [ org.gradle.status = release (not requested) org.gradle.usage = java-api - org.gradle.component.category = library (not requested) + org.gradle.category = library (not requested) Requested attributes not found in the selected variant: org.gradle.dependency.bundling = external + org.gradle.jvm.version = 11 ] org.slf4j:slf4j-log4j12:1.6.1 diff --git a/subprojects/docs/src/samples/userguide/organizingGradleProjects/separatedTestTypes/kotlin/gradle/integration-test.gradle.kts b/subprojects/docs/src/samples/userguide/organizingGradleProjects/separatedTestTypes/kotlin/gradle/integration-test.gradle.kts index 1106fd8c9ebee..edad36b7cb9a9 100644 --- a/subprojects/docs/src/samples/userguide/organizingGradleProjects/separatedTestTypes/kotlin/gradle/integration-test.gradle.kts +++ b/subprojects/docs/src/samples/userguide/organizingGradleProjects/separatedTestTypes/kotlin/gradle/integration-test.gradle.kts @@ -1,6 +1,6 @@ +// tag::custom-source-set[] val sourceSets = the() -// tag::custom-source-set[] sourceSets { create("integTest") { java.srcDir(file("src/integTest/java")) diff --git a/subprojects/execution/execution.gradle.kts b/subprojects/execution/execution.gradle.kts index 2c3964740bb3b..44d2e50955281 100644 --- a/subprojects/execution/execution.gradle.kts +++ b/subprojects/execution/execution.gradle.kts @@ -32,6 +32,7 @@ dependencies { implementation(project(":baseServices")) implementation(project(":snapshots")) implementation(project(":buildCachePackaging")) + implementation(library("commons_io")) implementation(library("commons_lang")) testImplementation(project(":internalTesting")) diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/IncrementalExecutionTest.groovy b/subprojects/execution/src/integTest/groovy/org/gradle/internal/execution/IncrementalExecutionIntegrationTest.groovy similarity index 78% rename from subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/IncrementalExecutionTest.groovy rename to subprojects/execution/src/integTest/groovy/org/gradle/internal/execution/IncrementalExecutionIntegrationTest.groovy index 6f9ff7eb15c09..81ec7bc2bc6d0 100644 --- a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/IncrementalExecutionTest.groovy +++ b/subprojects/execution/src/integTest/groovy/org/gradle/internal/execution/IncrementalExecutionIntegrationTest.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps +package org.gradle.internal.execution import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableSortedMap @@ -23,23 +23,27 @@ import org.gradle.api.file.FileCollection import org.gradle.api.internal.cache.StringInterner import org.gradle.api.internal.file.TestFiles import org.gradle.api.internal.file.collections.ImmutableFileCollection -import org.gradle.api.internal.model.NamedObjectInstantiator import org.gradle.caching.internal.CacheableEntity -import org.gradle.caching.internal.origin.OriginMetadata import org.gradle.internal.classloader.ClassLoaderHierarchyHasher -import org.gradle.internal.execution.CacheHandler -import org.gradle.internal.execution.ExecutionException -import org.gradle.internal.execution.ExecutionOutcome -import org.gradle.internal.execution.OutputChangeListener -import org.gradle.internal.execution.Result -import org.gradle.internal.execution.TestExecutionHistoryStore -import org.gradle.internal.execution.TestOutputFilesRepository -import org.gradle.internal.execution.UnitOfWork -import org.gradle.internal.execution.WorkExecutor -import org.gradle.internal.execution.history.changes.DefaultExecutionStateChanges -import org.gradle.internal.execution.history.changes.ExecutionStateChanges +import org.gradle.internal.execution.caching.CachingDisabledReason +import org.gradle.internal.execution.caching.CachingState +import org.gradle.internal.execution.history.AfterPreviousExecutionState +import org.gradle.internal.execution.history.BeforeExecutionState +import org.gradle.internal.execution.history.ExecutionHistoryStore +import org.gradle.internal.execution.history.OutputFilesRepository +import org.gradle.internal.execution.history.changes.DefaultExecutionStateChangeDetector +import org.gradle.internal.execution.history.changes.InputChangesInternal import org.gradle.internal.execution.history.impl.DefaultBeforeExecutionState import org.gradle.internal.execution.impl.DefaultWorkExecutor +import org.gradle.internal.execution.steps.BroadcastChangingOutputsStep +import org.gradle.internal.execution.steps.CatchExceptionStep +import org.gradle.internal.execution.steps.CreateOutputsStep +import org.gradle.internal.execution.steps.ExecuteStep +import org.gradle.internal.execution.steps.RecordOutputsStep +import org.gradle.internal.execution.steps.ResolveChangesStep +import org.gradle.internal.execution.steps.SkipUpToDateStep +import org.gradle.internal.execution.steps.SnapshotOutputsStep +import org.gradle.internal.execution.steps.StoreSnapshotsStep import org.gradle.internal.file.TreeType import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint import org.gradle.internal.fingerprint.impl.AbsolutePathFileCollectionFingerprinter @@ -59,13 +63,14 @@ import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider import org.gradle.testing.internal.util.Specification import org.junit.Rule +import javax.annotation.Nullable import java.time.Duration -import java.util.function.BooleanSupplier +import java.util.function.Supplier -import static org.gradle.internal.execution.ExecutionOutcome.EXECUTED +import static org.gradle.internal.execution.ExecutionOutcome.EXECUTED_NON_INCREMENTALLY import static org.gradle.internal.execution.ExecutionOutcome.UP_TO_DATE -class IncrementalExecutionTest extends Specification { +class IncrementalExecutionIntegrationTest extends Specification { @Rule final TestNameTestDirectoryProvider temporaryFolder = TestNameTestDirectoryProvider.newInstance() @@ -87,7 +92,6 @@ class IncrementalExecutionTest extends Specification { fileSystemMirror.beforeOutputChange(affectedOutputPaths) } } - def outputFilesRepository = new TestOutputFilesRepository() def buildInvocationScopeId = new BuildInvocationScopeId(UniqueId.generate()) def classloaderHierarchyHasher = new ClassLoaderHierarchyHasher() { @Override @@ -95,7 +99,10 @@ class IncrementalExecutionTest extends Specification { return HashCode.fromInt(1234) } } - def valueSnapshotter = new DefaultValueSnapshotter(classloaderHierarchyHasher, new NamedObjectInstantiator()) + def outputFilesRepository = Stub(OutputFilesRepository) { + isGeneratedByGradle() >> true + } + def valueSnapshotter = new DefaultValueSnapshotter(classloaderHierarchyHasher) final outputFile = temporaryFolder.file("output-file") final outputDir = temporaryFolder.file("output-dir") @@ -115,15 +122,22 @@ class IncrementalExecutionTest extends Specification { def unitOfWork = builder.build() - - WorkExecutor getExecutor() { - new DefaultWorkExecutor( - new SkipUpToDateStep( - new StoreSnapshotsStep(outputFilesRepository, - new SnapshotOutputStep(buildInvocationScopeId.getId(), - new CreateOutputsStep( - new CatchExceptionStep( - new ExecuteStep(outputChangeListener) + def changeDetector = new DefaultExecutionStateChangeDetector() + + WorkExecutor getExecutor() { + new DefaultWorkExecutor<>( + new ResolveChangesStep<>(changeDetector, + new SkipUpToDateStep<>( + new RecordOutputsStep<>(outputFilesRepository, + new BroadcastChangingOutputsStep<>(outputChangeListener, + new StoreSnapshotsStep<>( + new SnapshotOutputsStep<>(buildInvocationScopeId.getId(), + new CreateOutputsStep<>( + new CatchExceptionStep<>( + new ExecuteStep<>() + ) + ) + ) ) ) ) @@ -140,14 +154,14 @@ class IncrementalExecutionTest extends Specification { "file": [file("parent/outFile")], "files": [file("parent1/outFile"), file("parent2/outputFile1"), file("parent2/outputFile2")], ).withWork { -> - true + UnitOfWork.WorkResult.DID_WORK }.build() when: def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused def allDirs = ["outDir", "outDir1", "outDir2"].collect { file(it) } @@ -166,7 +180,7 @@ class IncrementalExecutionTest extends Specification { def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused result.finalOutputs.keySet() == ["dir", "emptyDir", "file", "missingDir", "missingFile"] as Set @@ -182,7 +196,7 @@ class IncrementalExecutionTest extends Specification { def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused def origin = result.originMetadata.buildInvocationId @@ -207,7 +221,7 @@ class IncrementalExecutionTest extends Specification { def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused def origin = result.originMetadata.buildInvocationId @@ -218,7 +232,7 @@ class IncrementalExecutionTest extends Specification { result = outOfDate(builder.build(), outputFilesChanged(file: [outputFile])) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused result.originMetadata.buildInvocationId == buildInvocationScopeId.id result.originMetadata.buildInvocationId != origin @@ -232,8 +246,7 @@ class IncrementalExecutionTest extends Specification { throw failure }.build()) then: - result.outcome.failure.get() instanceof ExecutionException - result.outcome.failure.get().cause == failure + result.outcome.failure.get() == failure !result.reused def origin = result.originMetadata.buildInvocationId @@ -242,7 +255,7 @@ class IncrementalExecutionTest extends Specification { result = outOfDate(builder.build(), "Task has failed previously.") then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused result.originMetadata.buildInvocationId == buildInvocationScopeId.id result.originMetadata.buildInvocationId != origin @@ -253,9 +266,9 @@ class IncrementalExecutionTest extends Specification { def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused - result.outOfDateReasons == ["No history is available."] + result.executionReasons == ["No history is available."] } def "out of date when output file removed"() { @@ -267,9 +280,9 @@ class IncrementalExecutionTest extends Specification { def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused - result.outOfDateReasons == ["Output property 'file' file ${outputFile.absolutePath} has been removed."] + result.executionReasons == ["Output property 'file' file ${outputFile.absolutePath} has been removed."] } def "out of date when output file in output dir removed"() { @@ -281,9 +294,9 @@ class IncrementalExecutionTest extends Specification { def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused - result.outOfDateReasons == ["Output property 'dir' file ${outputDirFile.absolutePath} has been removed."] + result.executionReasons == ["Output property 'dir' file ${outputDirFile.absolutePath} has been removed."] } def "out of date when output file has changed type"() { @@ -296,9 +309,9 @@ class IncrementalExecutionTest extends Specification { def result = execute(unitOfWork) then: - result.outcome.failure.get().message == "Execution failed for Test unit of work." + !result.outcome.successful !result.reused - result.outOfDateReasons == ["Output property 'file' file ${outputFile.absolutePath} has changed."] + result.executionReasons == ["Output property 'file' file ${outputFile.absolutePath} has changed."] } def "out of date when any file in output dir has changed type"() { @@ -311,9 +324,9 @@ class IncrementalExecutionTest extends Specification { def result = execute(unitOfWork) then: - result.outcome.failure.get().message == "Execution failed for Test unit of work." + !result.outcome.successful !result.reused - result.outOfDateReasons == ["Output property 'dir' file ${outputDirFile.absolutePath} has changed."] + result.executionReasons == ["Output property 'dir' file ${outputDirFile.absolutePath} has changed."] } def "out of date when any output file has changed contents"() { @@ -324,9 +337,9 @@ class IncrementalExecutionTest extends Specification { outputFile << "new content" def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused - result.outOfDateReasons == ["Output property 'file' file ${outputFile.absolutePath} has changed."] + result.executionReasons == ["Output property 'file' file ${outputFile.absolutePath} has changed."] } def "out of date when any file in output dir has changed contents"() { @@ -337,9 +350,9 @@ class IncrementalExecutionTest extends Specification { outputDirFile << "new content" def result = execute(unitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused - result.outOfDateReasons == ["Output property 'dir' file ${outputDirFile.absolutePath} has changed."] + result.executionReasons == ["Output property 'dir' file ${outputDirFile.absolutePath} has changed."] } def "out-of-date when any output files properties are added"() { @@ -361,9 +374,9 @@ class IncrementalExecutionTest extends Specification { def result = execute(outputFilesRemovedUnitOfWork) then: - result.outcome.get() == EXECUTED + result.outcome.get() == EXECUTED_NON_INCREMENTALLY !result.reused - result.outOfDateReasons == ["Output property 'file' has been removed for ${outputFilesRemovedUnitOfWork.displayName}"] + result.executionReasons == ["Output property 'file' has been removed for ${outputFilesRemovedUnitOfWork.displayName}"] } def "out-of-date when implementation changes"() { @@ -607,27 +620,56 @@ class IncrementalExecutionTest extends Specification { } } - UpToDateResult outOfDate(UnitOfWork unitOfWork, String... expectedReasons) { + UpToDateResult outOfDate(TestUnitOfWork unitOfWork, String... expectedReasons) { return outOfDate(unitOfWork, ImmutableList.copyOf(expectedReasons)) } - UpToDateResult outOfDate(UnitOfWork unitOfWork, List expectedReasons) { + UpToDateResult outOfDate(TestUnitOfWork unitOfWork, List expectedReasons) { def result = execute(unitOfWork) - assert result.outcome.get() == EXECUTED + assert result.outcome.get() == EXECUTED_NON_INCREMENTALLY assert !result.reused - assert result.outOfDateReasons == expectedReasons + assert result.executionReasons == expectedReasons return result } - UpToDateResult upToDate(UnitOfWork unitOfWork) { + UpToDateResult upToDate(TestUnitOfWork unitOfWork) { def result = execute(unitOfWork) assert result.outcome.get() == UP_TO_DATE return result } - UpToDateResult execute(UnitOfWork unitOfWork) { + UpToDateResult execute(TestUnitOfWork unitOfWork) { fileSystemMirror.beforeBuildFinished() - executor.execute(unitOfWork) + + def afterPreviousExecutionState = executionHistoryStore.load(unitOfWork.identity) + def beforeExecutionState = unitOfWork.beforeExecutionState + + executor.execute(new CachingContext() { + @Override + UnitOfWork getWork() { + unitOfWork + } + + @Override + Optional getRebuildReason() { + Optional.empty() + } + + @Override + Optional getAfterPreviousExecutionState() { + afterPreviousExecutionState + } + + @Override + Optional getBeforeExecutionState() { + Optional.of(beforeExecutionState) + } + + @Override + CachingState getCachingState() { + CachingState.NOT_DETERMINED + } + }) } private TestFile file(Object... path) { @@ -656,22 +698,26 @@ class IncrementalExecutionTest extends Specification { new UnitOfWorkBuilder() } + interface TestUnitOfWork extends UnitOfWork { + BeforeExecutionState getBeforeExecutionState() + } + class UnitOfWorkBuilder { - private BooleanSupplier work = { -> + private Supplier work = { -> create.each { it -> it.createFile() } - return true + return UnitOfWork.WorkResult.DID_WORK } private Map inputProperties = [prop: "value"] private Map> inputs = inputFiles - private Map> outputFiles = IncrementalExecutionTest.this.outputFiles - private Map> outputDirs = IncrementalExecutionTest.this.outputDirs + private Map> outputFiles = IncrementalExecutionIntegrationTest.this.outputFiles + private Map> outputDirs = IncrementalExecutionIntegrationTest.this.outputDirs private Collection create = createFiles private ImplementationSnapshot implementation = ImplementationSnapshot.of(UnitOfWork.name, HashCode.fromInt(1234)) private - UnitOfWorkBuilder withWork(BooleanSupplier closure) { + UnitOfWorkBuilder withWork(Supplier closure) { work = closure return this } @@ -724,21 +770,34 @@ class IncrementalExecutionTest extends Specification { return this } - UnitOfWork build() { + TestUnitOfWork build() { def outputFileSpecs = outputFiles.collectEntries { key, value -> [(key): outputFileSpec(*value)] } def outputDirSpecs = outputDirs.collectEntries { key, value -> [(key): outputDirectorySpec(*value)]} - return new UnitOfWork() { + return new TestUnitOfWork() { private final Map outputs = outputFileSpecs + outputDirSpecs - private final ImplementationSnapshot implementationSnapshot = implementation - private final ImmutableList additionalImplementationSnapshots = ImmutableList.of() - Optional changes boolean executed @Override - ExecutionOutcome execute() { + UnitOfWork.WorkResult execute(@Nullable InputChangesInternal inputChanges) { executed = true - return work.asBoolean ? EXECUTED : UP_TO_DATE + return work.get() + } + + @Override + BeforeExecutionState getBeforeExecutionState() { + new DefaultBeforeExecutionState( + implementation, + ImmutableList.of(), + snapshotInputProperties(), + snapshotInputFiles(), + snapshotOutputs() + ) + } + + @Override + ExecutionHistoryStore getExecutionHistoryStore() { + return IncrementalExecutionIntegrationTest.this.executionHistoryStore } @Override @@ -746,6 +805,13 @@ class IncrementalExecutionTest extends Specification { throw new UnsupportedOperationException() } + @Override + void visitInputFileProperties(UnitOfWork.InputFilePropertyVisitor visitor) { + for (entry in inputs.entrySet()) { + visitor.visitInputFileProperty(entry.key, entry.value, false) + } + } + @Override void visitOutputProperties(UnitOfWork.OutputPropertyVisitor visitor) { outputs.forEach { name, spec -> @@ -754,37 +820,28 @@ class IncrementalExecutionTest extends Specification { } @Override - long markExecutionTime() { - 0 + boolean isAllowOverlappingOutputs() { + return true } @Override - void visitLocalState(CacheableEntity.LocalStateVisitor visitor) { - throw new UnsupportedOperationException() + long markExecutionTime() { + 0 } @Override - void outputsRemovedAfterFailureToLoadFromCache() { + void visitLocalState(UnitOfWork.LocalStateVisitor visitor) { throw new UnsupportedOperationException() } @Override - CacheHandler createCacheHandler() { + Optional shouldDisableCaching() { throw new UnsupportedOperationException() } @Override - void persistResult(ImmutableSortedMap finalOutputs, boolean successful, OriginMetadata originMetadata) { - executionHistoryStore.store( - getIdentity(), - originMetadata, - implementationSnapshot, - additionalImplementationSnapshots, - snapshotInputProperties(), - snapshotInputFiles(), - finalOutputs, - successful - ) + boolean isAllowedToLoadFromCache() { + throw new UnsupportedOperationException() } @Override @@ -809,16 +866,6 @@ class IncrementalExecutionTest extends Specification { ImplementationSnapshot implementationSnapshot = implementation - - @Override - Optional getChangesSincePreviousExecution() { - changes = executionHistoryStore.load(getIdentity()).map { previous -> - def outputsBefore = snapshotOutputs() - def beforeExecutionState = new DefaultBeforeExecutionState(implementationSnapshot, additionalImplementationSnapshots, snapshotInputProperties(), snapshotInputFiles(), outputsBefore) - return new DefaultExecutionStateChanges(previous, beforeExecutionState, this) - } - } - private ImmutableSortedMap snapshotInputProperties() { def builder = ImmutableSortedMap.naturalOrder() inputProperties.each { propertyName, value -> @@ -841,6 +888,16 @@ class IncrementalExecutionTest extends Specification { snapshotOutputs() } + @Override + boolean isRequiresInputChanges() { + return false + } + + @Override + boolean isRequiresLegacyInputChanges() { + return false + } + private ImmutableSortedMap snapshotOutputs() { def builder = ImmutableSortedMap.naturalOrder() outputs.each { propertyName, spec -> diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/CacheHandler.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/CachingContext.java similarity index 64% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/CacheHandler.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/CachingContext.java index 2b459a5283733..fdb186a68339a 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/CacheHandler.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/CachingContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,13 +16,11 @@ package org.gradle.internal.execution; -import org.gradle.caching.BuildCacheKey; +import org.gradle.internal.execution.caching.CachingState; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; - -public interface CacheHandler { - Optional load(Function loader); - void store(Consumer storer); +public interface CachingContext extends IncrementalContext { + /** + * The resolved state of caching for the work. + */ + CachingState getCachingState(); } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/CachingResult.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/CachingResult.java new file mode 100644 index 0000000000000..d4574b7307b91 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/CachingResult.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution; + +import org.gradle.internal.execution.caching.CachingState; + +public interface CachingResult extends UpToDateResult { + CachingState getCachingState(); +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/Context.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/Context.java similarity index 87% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/Context.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/Context.java index 55ddd076ab1fd..8bd33e541d48b 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/Context.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/Context.java @@ -14,9 +14,7 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; - -import org.gradle.internal.execution.UnitOfWork; +package org.gradle.internal.execution; public interface Context { UnitOfWork getWork(); diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CurrentSnapshotResult.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/CurrentSnapshotResult.java similarity index 94% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CurrentSnapshotResult.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/CurrentSnapshotResult.java index 0e3572fc7ed9a..f9b99d406d7a9 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CurrentSnapshotResult.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/CurrentSnapshotResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution; import com.google.common.collect.ImmutableSortedMap; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/ExecutionOutcome.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/ExecutionOutcome.java index 48ebd2e31e61d..c8684c93bff83 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/ExecutionOutcome.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/ExecutionOutcome.java @@ -17,7 +17,25 @@ package org.gradle.internal.execution; public enum ExecutionOutcome { + /** + * The outputs haven't been changed, because the work is already up-to-date + * (i.e. its inputs and outputs match that of the previous execution in the + * same workspace). + */ UP_TO_DATE, + + /** + * The outputs of the work have been loaded from the build cache. + */ FROM_CACHE, - EXECUTED + + /** + * The work has been executed with information about the changes that happened since the previous execution. + */ + EXECUTED_INCREMENTALLY, + + /** + * The work has been executed with no incremental change information. + */ + EXECUTED_NON_INCREMENTALLY } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/IncrementalChangesContext.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/IncrementalChangesContext.java new file mode 100644 index 0000000000000..a60d509ac6b8d --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/IncrementalChangesContext.java @@ -0,0 +1,29 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution; + +import org.gradle.internal.execution.history.changes.ExecutionStateChanges; + +import java.util.Optional; + +public interface IncrementalChangesContext extends CachingContext { + /** + * Returns changes detected between the execution state after the last execution and before the current execution. + * Empty if changes couldn't be detected (e.g. because history was unavailable). + */ + Optional getChanges(); +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/IncrementalContext.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/IncrementalContext.java new file mode 100644 index 0000000000000..0e22eb6d9c4d2 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/IncrementalContext.java @@ -0,0 +1,41 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution; + +import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import org.gradle.internal.execution.history.BeforeExecutionState; + +import java.util.Optional; + +public interface IncrementalContext extends Context { + /** + * If incremental mode is disabled, this returns the reason, otherwise it's empty. + */ + Optional getRebuildReason(); + + /** + * Returns the execution state after the previous execution if available. + * Empty when execution history is not available. + */ + Optional getAfterPreviousExecutionState(); + + /** + * Returns the execution state before execution. + * Empty if execution state was not observed before execution. + */ + Optional getBeforeExecutionState(); +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SnapshotResult.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/SnapshotResult.java similarity index 91% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SnapshotResult.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/SnapshotResult.java index 1b369970485ef..dc3b7e5b0538b 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SnapshotResult.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/SnapshotResult.java @@ -14,11 +14,10 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution; import com.google.common.collect.ImmutableSortedMap; import org.gradle.caching.internal.origin.OriginMetadata; -import org.gradle.internal.execution.Result; import org.gradle.internal.fingerprint.FileCollectionFingerprint; public interface SnapshotResult extends Result { diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/Step.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/Step.java similarity index 88% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/Step.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/Step.java index f8a237f634dc5..bf64ff38552cf 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/Step.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/Step.java @@ -14,9 +14,7 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; - -import org.gradle.internal.execution.Result; +package org.gradle.internal.execution; public interface Step { R execute(C context); diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/UnitOfWork.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/UnitOfWork.java index 4a994f68595a3..e0eb1f03201f8 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/UnitOfWork.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/UnitOfWork.java @@ -19,11 +19,15 @@ import com.google.common.collect.ImmutableSortedMap; import org.gradle.api.file.FileCollection; import org.gradle.caching.internal.CacheableEntity; -import org.gradle.caching.internal.origin.OriginMetadata; -import org.gradle.internal.execution.history.changes.ExecutionStateChanges; +import org.gradle.internal.execution.caching.CachingDisabledReason; +import org.gradle.internal.execution.caching.CachingState; +import org.gradle.internal.execution.history.ExecutionHistoryStore; +import org.gradle.internal.execution.history.changes.InputChangesInternal; import org.gradle.internal.file.TreeType; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import javax.annotation.Nullable; +import java.io.File; import java.time.Duration; import java.util.Optional; @@ -32,24 +36,43 @@ public interface UnitOfWork extends CacheableEntity { /** * Executes the work synchronously. */ - ExecutionOutcome execute(); + WorkResult execute(@Nullable InputChangesInternal inputChanges); Optional getTimeout(); + boolean isRequiresInputChanges(); + + boolean isRequiresLegacyInputChanges(); + + void visitInputFileProperties(InputFilePropertyVisitor visitor); + void visitOutputProperties(OutputPropertyVisitor visitor); + void visitLocalState(LocalStateVisitor visitor); + + @FunctionalInterface + interface LocalStateVisitor { + void visitLocalStateRoot(File localStateRoot); + } + long markExecutionTime(); /** - * Loading from cache failed and all outputs were removed. + * Return a reason to disable caching for this work. + * When returning {@link Optional#empty()} if caching can still be disabled further down the pipeline. */ - void outputsRemovedAfterFailureToLoadFromCache(); - - CacheHandler createCacheHandler(); + Optional shouldDisableCaching(); - void persistResult(ImmutableSortedMap finalOutputs, boolean successful, OriginMetadata originMetadata); + /** + * This is a temporary measure for Gradle tasks to track a legacy measurement of all input snapshotting together. + */ + default void markSnapshottingInputsFinished(CachingState cachingState) {} - Optional getChangesSincePreviousExecution(); + /** + * Is this work item allowed to load from the cache, or if we only allow it to be stored. + */ + // TODO Make this part of CachingState instead + boolean isAllowedToLoadFromCache(); /** * Paths to locations changed by the unit of work. @@ -63,10 +86,27 @@ public interface UnitOfWork extends CacheableEntity { */ Optional> getChangingOutputs(); + /** + * When overlapping outputs are allowed, output files added between executions are ignored during change detection. + */ + boolean isAllowOverlappingOutputs(); + + @FunctionalInterface + interface InputFilePropertyVisitor { + void visitInputFileProperty(String name, @Nullable Object value, boolean incremental); + } + @FunctionalInterface interface OutputPropertyVisitor { void visitOutputProperty(String name, TreeType type, FileCollection roots); } + enum WorkResult { + DID_WORK, + DID_NO_WORK + } + + ExecutionHistoryStore getExecutionHistoryStore(); + ImmutableSortedMap snapshotAfterOutputsGenerated(); } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/UpToDateResult.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/UpToDateResult.java similarity index 90% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/UpToDateResult.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/UpToDateResult.java index 2333ed9e0f639..2dfc8144a9d70 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/UpToDateResult.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/UpToDateResult.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution; import com.google.common.collect.ImmutableList; @@ -23,5 +23,5 @@ public interface UpToDateResult extends SnapshotResult { * A list of messages describing the first few reasons encountered that caused the work to be executed. * An empty list means the work was up-to-date and hasn't been executed. */ - ImmutableList getOutOfDateReasons(); + ImmutableList getExecutionReasons(); } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/WorkExecutor.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/WorkExecutor.java index d987e8367153e..e0dd5bcd042e0 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/WorkExecutor.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/WorkExecutor.java @@ -16,6 +16,6 @@ package org.gradle.internal.execution; -public interface WorkExecutor { - R execute(UnitOfWork work); +public interface WorkExecutor { + R execute(C context); } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingDisabledReason.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingDisabledReason.java new file mode 100644 index 0000000000000..316211598f511 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingDisabledReason.java @@ -0,0 +1,35 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.caching; + +public class CachingDisabledReason { + private final CachingDisabledReasonCategory category; + private final String message; + + public CachingDisabledReason(CachingDisabledReasonCategory category, String message) { + this.category = category; + this.message = message; + } + + public CachingDisabledReasonCategory getCategory() { + return category; + } + + public String getMessage() { + return message; + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingDisabledReasonCategory.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingDisabledReasonCategory.java new file mode 100644 index 0000000000000..13122b333a058 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingDisabledReasonCategory.java @@ -0,0 +1,98 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.caching; + +public enum CachingDisabledReasonCategory { + /** + * Reason for disabled caching is not known. + */ + UNKNOWN, + + /** + * Caching has not been enabled for the build. + */ + BUILD_CACHE_DISABLED, + + /** + * Caching has not been enabled for the work. + */ + NOT_CACHEABLE, + + /** + * Condition enabling caching isn't satisfied. + */ + ENABLE_CONDITION_NOT_SATISFIED, + + /** + * Condition disabling caching satisfied. + */ + DISABLE_CONDITION_SATISFIED, + + /** + * The work has no outputs declared. + */ + NO_OUTPUTS_DECLARED, + + /** + * Work has declared output that is not cacheable. + */ + NON_CACHEABLE_OUTPUT, + + /** + * Work's outputs overlap with other work's. + */ + OVERLAPPING_OUTPUTS, + + /** + * The work's implementation is not cacheable. + * + * Reasons for non-cacheable implementations: + *
      + *
    • the type is loaded via an unknown classloader,
    • + *
    • a Java lambda was used.
    • + *
    + * + * @see How fingerprinting works + */ + NON_CACHEABLE_IMPLEMENTATION, + + /** + * Additional implementation is not cacheable. Reasons for non-cacheable task action: + * + * Reasons for non-cacheable implementations: + *
      + *
    • the type is loaded via an unknown classloader,
    • + *
    • a Java lambda was used.
    • + *
    + * + * @see How fingerprinting works + */ + NON_CACHEABLE_ADDITIONAL_IMPLEMENTATION, + + /** + * One of the work's inputs is not cacheable. + * + * Reasons for non-cacheable inputs: + *
      + *
    • some type used as an input is loaded via an unknown classloader,
    • + *
    • a Java lambda was used as an input.
    • + *
    + * + * @see How fingerprinting works + */ + NON_CACHEABLE_INPUTS +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingInputs.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingInputs.java new file mode 100644 index 0000000000000..e999ca78168a5 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingInputs.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.caching; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.ImmutableSortedSet; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.hash.HashCode; +import org.gradle.internal.snapshot.impl.ImplementationSnapshot; + +public interface CachingInputs { + + /** + * The snapshot of the main implementation. + */ + ImplementationSnapshot getImplementation(); + + /** + * Any additional implementation present. + */ + ImmutableList getAdditionalImplementations(); + + /** + * Input value fingerprints. + */ + ImmutableSortedMap getInputValueFingerprints(); + + /** + * Input file fingerprints. + */ + ImmutableSortedMap getInputFileFingerprints(); + + /** + * Names of the output properties of the work. + */ + ImmutableSortedSet getOutputProperties(); + + /** + * A list of input value property names that were not cacheable. + */ + ImmutableSortedSet getNonCacheableInputProperties(); +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingState.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingState.java new file mode 100644 index 0000000000000..e4d217a2083ae --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingState.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.caching; + +import com.google.common.collect.ImmutableList; +import org.gradle.caching.BuildCacheKey; + +import java.util.Optional; + +public interface CachingState { + /** + * The cache key if a valid cache key could be built. Might be present even when caching is disabled. + */ + Optional getKey(); + + /** + * Reasons for the caching to be disabled for the work, empty when enabled. + * If empty, {@link #getKey()} is never empty. + */ + ImmutableList getDisabledReasons(); + + /** + * Individual fingerprints for each of the work's inputs. + */ + Optional getInputs(); + + CachingState NOT_DETERMINED = disabledWithoutInputs(new CachingDisabledReason(CachingDisabledReasonCategory.UNKNOWN, "Cacheability was not determined")); + + static CachingState disabledWithoutInputs(CachingDisabledReason reason) { + ImmutableList reasons = ImmutableList.of(reason); + return new CachingState() { + @Override + public Optional getKey() { + return Optional.empty(); + } + + @Override + public ImmutableList getDisabledReasons() { + return reasons; + } + + @Override + public Optional getInputs() { + return Optional.empty(); + } + }; + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingStateBuilder.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingStateBuilder.java new file mode 100644 index 0000000000000..06f8594082b6c --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/CachingStateBuilder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.caching; + +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.snapshot.ValueSnapshot; +import org.gradle.internal.snapshot.impl.ImplementationSnapshot; + +import java.util.Map; + +public interface CachingStateBuilder { + void withImplementation(ImplementationSnapshot implementation); + + void withAdditionalImplementations(Iterable additionalImplementations); + + void withInputValueFingerprints(Map fingerprints); + + void withInputFilePropertyFingerprints(Map fingerprints); + + void withOutputPropertyNames(Iterable propertyNames); + + void markNotCacheable(CachingDisabledReason reason); + + CachingState build(); +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/DefaultCachingStateBuilder.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/DefaultCachingStateBuilder.java new file mode 100644 index 0000000000000..73846605c6595 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/DefaultCachingStateBuilder.java @@ -0,0 +1,326 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.caching.impl; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.ImmutableSortedSet; +import org.gradle.caching.BuildCacheKey; +import org.gradle.internal.execution.caching.CachingDisabledReason; +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory; +import org.gradle.internal.execution.caching.CachingInputs; +import org.gradle.internal.execution.caching.CachingState; +import org.gradle.internal.execution.caching.CachingStateBuilder; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.hash.HashCode; +import org.gradle.internal.hash.Hasher; +import org.gradle.internal.hash.Hashing; +import org.gradle.internal.snapshot.ValueSnapshot; +import org.gradle.internal.snapshot.impl.ImplementationSnapshot; + +import javax.annotation.Nullable; +import javax.annotation.OverridingMethodsMustInvokeSuper; +import java.util.Map; +import java.util.Optional; + +import static org.gradle.internal.execution.caching.CachingDisabledReasonCategory.NON_CACHEABLE_ADDITIONAL_IMPLEMENTATION; +import static org.gradle.internal.execution.caching.CachingDisabledReasonCategory.NON_CACHEABLE_IMPLEMENTATION; + +public class DefaultCachingStateBuilder implements CachingStateBuilder { + private ImplementationSnapshot implementation; + private ImmutableList additionalImplementations = ImmutableList.of(); + private final ImmutableSortedMap.Builder inputValueFingerprintsBuilder = ImmutableSortedMap.naturalOrder(); + private ImmutableSortedMap inputFileFingerprints = ImmutableSortedMap.of(); + private final ImmutableSortedMap.Builder nonCacheableInputPropertiesBuilder = ImmutableSortedMap.naturalOrder(); + private ImmutableSortedSet outputProperties = ImmutableSortedSet.of(); + private final ImmutableList.Builder noCachingReasonsBuilder = ImmutableList.builder(); + + @Override + public final void withImplementation(ImplementationSnapshot implementation) { + this.implementation = implementation; + processImplementation(implementation); + } + + @OverridingMethodsMustInvokeSuper + protected void processImplementation(ImplementationSnapshot implementation) { + if (implementation.isUnknown()) { + noCachingReasonsBuilder.add(new CachingDisabledReason( + NON_CACHEABLE_IMPLEMENTATION, + "Implementation type " + implementation.getUnknownReason() + )); + } + } + + @Override + public final void withAdditionalImplementations(Iterable additionalImplementations) { + this.additionalImplementations = ImmutableList.copyOf(additionalImplementations); + for (ImplementationSnapshot additionalImplementation : additionalImplementations) { + processAdditionalImplementation(additionalImplementation); + } + } + + @OverridingMethodsMustInvokeSuper + protected void processAdditionalImplementation(ImplementationSnapshot additionalImplementation) { + if (additionalImplementation.isUnknown()) { + noCachingReasonsBuilder.add(new CachingDisabledReason( + NON_CACHEABLE_ADDITIONAL_IMPLEMENTATION, + "Additional implementation type " + additionalImplementation.getUnknownReason() + )); + } + } + + @Override + public final void withInputValueFingerprints(Map fingerprints) { + fingerprints.forEach((propertyName, fingerprint) -> { + Hasher hasher = Hashing.newHasher(); + fingerprint.appendToHasher(hasher); + if (hasher.isValid()) { + HashCode hash = hasher.hash(); + recordInputValueFingerprint(propertyName, hash); + } else { + markInputValuePropertyNotCacheable(propertyName, hasher.getInvalidReason()); + } + }); + } + + @OverridingMethodsMustInvokeSuper + protected void recordInputValueFingerprint(String propertyName, HashCode fingerprint) { + inputValueFingerprintsBuilder.put(propertyName, fingerprint); + } + + @OverridingMethodsMustInvokeSuper + protected void markInputValuePropertyNotCacheable(String propertyName, String nonCacheableReason) { + nonCacheableInputPropertiesBuilder.put(propertyName, nonCacheableReason); + } + + @Override + @OverridingMethodsMustInvokeSuper + public void withInputFilePropertyFingerprints(Map fingerprints) { + this.inputFileFingerprints = ImmutableSortedMap.copyOf(fingerprints); + } + + @Override + @OverridingMethodsMustInvokeSuper + public void withOutputPropertyNames(Iterable propertyNames) { + this.outputProperties = ImmutableSortedSet.copyOf(propertyNames); + } + + @Override + @OverridingMethodsMustInvokeSuper + public void markNotCacheable(CachingDisabledReason reason) { + noCachingReasonsBuilder.add(reason); + } + + @Override + public final CachingState build() { + ImmutableSortedMap inputValueFingerprints = inputValueFingerprintsBuilder.build(); + + Hasher hasher = Hashing.newHasher(); + implementation.appendToHasher(hasher); + additionalImplementations.forEach(additionalImplementation -> { + additionalImplementation.appendToHasher(hasher); + }); + + inputValueFingerprints.forEach((propertyName, fingerprint) -> { + hasher.putString(propertyName); + hasher.putHash(fingerprint); + }); + + inputFileFingerprints.forEach((propertyName, fingerprint) -> { + hasher.putString(propertyName); + hasher.putHash(fingerprint.getHash()); + }); + + outputProperties.forEach(propertyName -> hasher.putString(propertyName)); + + ImmutableSortedMap nonCacheableInputPropertiesMap = nonCacheableInputPropertiesBuilder.build(); + if (!nonCacheableInputPropertiesMap.isEmpty()) { + StringBuilder builder = new StringBuilder("Non-cacheable inputs: "); + boolean first = true; + for (Map.Entry entry : nonCacheableInputPropertiesMap.entrySet()) { + if (!first) { + builder.append(", "); + } + first = false; + builder + .append("property '") + .append(entry.getKey()) + .append("' ") + .append(entry.getValue()); + } + String message = builder.toString(); + noCachingReasonsBuilder.add(new CachingDisabledReason( + CachingDisabledReasonCategory.NON_CACHEABLE_INPUTS, + message + )); + hasher.markAsInvalid(message); + } + ImmutableSortedSet nonCacheableInputProperties = nonCacheableInputPropertiesMap.keySet(); + + CachingInputs inputs = new DefaultCachingInputs( + implementation, + additionalImplementations, + inputValueFingerprints, + inputFileFingerprints, + outputProperties, + nonCacheableInputProperties + ); + + ImmutableList cachingDisabledReasons = noCachingReasonsBuilder.build(); + + if (cachingDisabledReasons.isEmpty()) { + return new CachedState(hasher.hash(), inputs); + } else { + HashCode key = hasher.isValid() + ? hasher.hash() + : null; + return new NonCachedState(key, cachingDisabledReasons, inputs); + } + } + + private static class CachedState implements CachingState { + private final BuildCacheKey key; + private final CachingInputs inputs; + + public CachedState(HashCode key, CachingInputs inputs) { + this.key = new DefaultBuildCacheKey(key); + this.inputs = inputs; + } + + @Override + public Optional getKey() { + return Optional.of(key); + } + + @Override + public ImmutableList getDisabledReasons() { + return ImmutableList.of(); + } + + @Override + public Optional getInputs() { + return Optional.of(inputs); + } + } + + private static class NonCachedState implements CachingState { + private final BuildCacheKey key; + private final ImmutableList disabledReasons; + private final CachingInputs inputs; + + public NonCachedState(@Nullable HashCode key, Iterable disabledReasons, CachingInputs inputs) { + this.key = key == null + ? null + : new DefaultBuildCacheKey(key); + this.disabledReasons = ImmutableList.copyOf(disabledReasons); + this.inputs = inputs; + } + + @Override + public Optional getKey() { + return Optional.ofNullable(key); + } + + @Override + public ImmutableList getDisabledReasons() { + return disabledReasons; + } + + @Override + public Optional getInputs() { + return Optional.of(inputs); + } + } + + private static class DefaultBuildCacheKey implements BuildCacheKey { + private final HashCode hashCode; + + public DefaultBuildCacheKey(HashCode hashCode) { + this.hashCode = hashCode; + } + + @Override + public String getHashCode() { + return hashCode.toString(); + } + + @Override + public byte[] toByteArray() { + return hashCode.toByteArray(); + } + + @Override + public String getDisplayName() { + return getHashCode(); + } + } + + private static class DefaultCachingInputs implements CachingInputs { + ImplementationSnapshot implementation; + ImmutableList additionalImplementations; + ImmutableSortedMap inputValueFingerprints; + ImmutableSortedMap inputFileFingerprints; + ImmutableSortedSet outputProperties; + ImmutableSortedSet nonCacheableInputProperties; + + public DefaultCachingInputs( + ImplementationSnapshot implementation, + ImmutableList additionalImplementations, + ImmutableSortedMap inputValueFingerprints, + ImmutableSortedMap inputFileFingerprints, + ImmutableSortedSet outputProperties, + ImmutableSortedSet nonCacheableInputProperties + ) { + this.implementation = implementation; + this.additionalImplementations = additionalImplementations; + this.inputValueFingerprints = inputValueFingerprints; + this.inputFileFingerprints = inputFileFingerprints; + this.outputProperties = outputProperties; + this.nonCacheableInputProperties = nonCacheableInputProperties; + } + + @Override + public ImplementationSnapshot getImplementation() { + return implementation; + } + + @Override + public ImmutableList getAdditionalImplementations() { + return additionalImplementations; + } + + @Override + public ImmutableSortedMap getInputValueFingerprints() { + return inputValueFingerprints; + } + + @Override + public ImmutableSortedMap getInputFileFingerprints() { + return inputFileFingerprints; + } + + @Override + public ImmutableSortedSet getOutputProperties() { + return outputProperties; + } + + @Override + public ImmutableSortedSet getNonCacheableInputProperties() { + return nonCacheableInputProperties; + } + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/LoggingCachingStateBuilder.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/LoggingCachingStateBuilder.java new file mode 100644 index 0000000000000..d9306d9e1a798 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/LoggingCachingStateBuilder.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.caching.impl; + +import org.gradle.internal.execution.caching.CachingDisabledReason; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.hash.HashCode; +import org.gradle.internal.snapshot.impl.ImplementationSnapshot; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; + +public class LoggingCachingStateBuilder extends DefaultCachingStateBuilder { + private static final Logger LOGGER = LoggerFactory.getLogger(LoggingCachingStateBuilder.class); + + @Override + protected void processImplementation(ImplementationSnapshot implementation) { + LOGGER.warn("Appending implementation to build cache key: {}", implementation); + super.processImplementation(implementation); + } + + @Override + protected void processAdditionalImplementation(ImplementationSnapshot additionalImplementation) { + LOGGER.warn("Appending additional implementation to build cache key: {}", additionalImplementation); + super.processAdditionalImplementation(additionalImplementation); + } + + @Override + protected void recordInputValueFingerprint(String propertyName, HashCode fingerprint) { + LOGGER.warn("Appending input value fingerprint for '{}' to build cache key: {}", propertyName, fingerprint); + super.recordInputValueFingerprint(propertyName, fingerprint); + } + + @Override + protected void markInputValuePropertyNotCacheable(String propertyName, String nonCacheableReason) { + LOGGER.warn("Non-cacheable input value property '{}' {}.", propertyName, nonCacheableReason); + super.markInputValuePropertyNotCacheable(propertyName, nonCacheableReason); + } + + @Override + public void withInputFilePropertyFingerprints(Map fingerprints) { + fingerprints.forEach((propertyName, fingerprint) -> { + LOGGER.warn("Appending input file fingerprints for '{}' to build cache key: {}", propertyName, fingerprint.getHash()); + }); + super.withInputFilePropertyFingerprints(fingerprints); + } + + @Override + public void withOutputPropertyNames(Iterable propertyNames) { + propertyNames.forEach(propertyName -> { + LOGGER.warn("Appending output property name to build cache key: {}", propertyName); + }); + super.withOutputPropertyNames(propertyNames); + } + + @Override + public void markNotCacheable(CachingDisabledReason reason) { + LOGGER.warn("Non-cacheable because {} [{}]", reason.getMessage(), reason.getCategory()); + super.markNotCacheable(reason); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/package-info.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/package-info.java new file mode 100644 index 0000000000000..9c2d4291b6bce --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/impl/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NonNullApi +package org.gradle.internal.execution.caching.impl; + +import org.gradle.api.NonNullApi; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/package-info.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/package-info.java new file mode 100644 index 0000000000000..4c6990417d8a6 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/caching/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NonNullApi +package org.gradle.internal.execution.caching; + +import org.gradle.api.NonNullApi; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/AbstractFingerprintChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/AbstractFingerprintChanges.java index 15ae9a4c347f1..16cd101df6942 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/AbstractFingerprintChanges.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/AbstractFingerprintChanges.java @@ -16,18 +16,19 @@ package org.gradle.internal.execution.history.changes; -import com.google.common.collect.ImmutableSortedMap; import org.gradle.internal.change.ChangeContainer; import org.gradle.internal.change.ChangeVisitor; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; import org.gradle.internal.fingerprint.FileCollectionFingerprint; +import java.util.SortedMap; + public abstract class AbstractFingerprintChanges implements ChangeContainer { - protected final ImmutableSortedMap previous; - protected final ImmutableSortedMap current; + protected final SortedMap previous; + protected final SortedMap current; private final String title; - protected AbstractFingerprintChanges(ImmutableSortedMap previous, ImmutableSortedMap current, String title) { + protected AbstractFingerprintChanges(SortedMap previous, SortedMap current, String title) { this.previous = previous; this.current = current; this.title = title; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultExecutionStateChangeDetector.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultExecutionStateChangeDetector.java new file mode 100644 index 0000000000000..30ce08e57d977 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultExecutionStateChangeDetector.java @@ -0,0 +1,212 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes; + +import com.google.common.collect.ImmutableCollection; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import org.gradle.api.Describable; +import org.gradle.internal.change.CachingChangeContainer; +import org.gradle.internal.change.Change; +import org.gradle.internal.change.ChangeContainer; +import org.gradle.internal.change.ChangeVisitor; +import org.gradle.internal.change.ErrorHandlingChangeContainer; +import org.gradle.internal.change.SummarizingChangeContainer; +import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import org.gradle.internal.execution.history.BeforeExecutionState; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; + +public class DefaultExecutionStateChangeDetector implements ExecutionStateChangeDetector { + @Override + public ExecutionStateChanges detectChanges(AfterPreviousExecutionState lastExecution, BeforeExecutionState thisExecution, Describable executable, boolean allowOverlappingOutputs, IncrementalInputProperties incrementalInputProperties) { + // Capture changes in execution outcome + ChangeContainer previousSuccessState = new PreviousSuccessChanges( + lastExecution.isSuccessful()); + + // Capture changes to implementation + ChangeContainer implementationChanges = new ImplementationChanges( + lastExecution.getImplementation(), lastExecution.getAdditionalImplementations(), + thisExecution.getImplementation(), thisExecution.getAdditionalImplementations(), + executable); + + // Capture non-file input changes + ChangeContainer inputPropertyChanges = new PropertyChanges( + lastExecution.getInputProperties(), + thisExecution.getInputProperties(), + "Input", + executable); + ChangeContainer inputPropertyValueChanges = new InputValueChanges( + lastExecution.getInputProperties(), + thisExecution.getInputProperties(), + executable); + + // Capture input files state + ChangeContainer inputFilePropertyChanges = new PropertyChanges( + lastExecution.getInputFileProperties(), + thisExecution.getInputFileProperties(), + "Input file", + executable); + InputFileChanges nonIncrementalInputFileChanges = incrementalInputProperties.nonIncrementalChanges( + lastExecution.getInputFileProperties(), + thisExecution.getInputFileProperties() + ); + InputFileChanges directIncrementalInputFileChanges = incrementalInputProperties.incrementalChanges( + lastExecution.getInputFileProperties(), + thisExecution.getInputFileProperties() + ); + InputFileChanges incrementalInputFileChanges = errorHandling(executable, caching(directIncrementalInputFileChanges)); + + // Capture output files state + ChangeContainer outputFilePropertyChanges = new PropertyChanges( + lastExecution.getOutputFileProperties(), + thisExecution.getOutputFileProperties(), + "Output", + executable); + OutputFileChanges outputFileChanges = new OutputFileChanges( + lastExecution.getOutputFileProperties(), + thisExecution.getOutputFileProperties(), + allowOverlappingOutputs); + + ChangeContainer rebuildTriggeringChanges = errorHandling(executable, new SummarizingChangeContainer(previousSuccessState, implementationChanges, inputPropertyChanges, inputPropertyValueChanges, outputFilePropertyChanges, outputFileChanges, inputFilePropertyChanges, nonIncrementalInputFileChanges)); + + ImmutableList.Builder builder = ImmutableList.builder(); + MessageCollectingChangeVisitor visitor = new MessageCollectingChangeVisitor(builder, ExecutionStateChangeDetector.MAX_OUT_OF_DATE_MESSAGES); + rebuildTriggeringChanges.accept(visitor); + ImmutableList rebuildReasons = builder.build(); + + boolean rebuildRequired = !rebuildReasons.isEmpty(); + + if (!rebuildRequired) { + incrementalInputFileChanges.accept(visitor); + } + + ImmutableList allChangeMessages = builder.build(); + return rebuildRequired + ? new NonIncrementalDetectedExecutionStateChanges(allChangeMessages, thisExecution.getInputFileProperties(), incrementalInputProperties) + : new IncrementalDetectedExecutionStateChanges(allChangeMessages, thisExecution.getInputFileProperties(), incrementalInputFileChanges, incrementalInputProperties); + } + + private static InputFileChanges caching(InputFileChanges wrapped) { + CachingChangeContainer cachingChangeContainer = new CachingChangeContainer(MAX_OUT_OF_DATE_MESSAGES, wrapped); + return new InputFileChangesWrapper(wrapped, cachingChangeContainer); + } + + private static ChangeContainer errorHandling(Describable executable, ChangeContainer wrapped) { + return new ErrorHandlingChangeContainer(executable, wrapped); + } + + private static InputFileChanges errorHandling(Describable executable, InputFileChanges wrapped) { + ErrorHandlingChangeContainer errorHandlingChangeContainer = new ErrorHandlingChangeContainer(executable, wrapped); + return new InputFileChangesWrapper(wrapped, errorHandlingChangeContainer); + } + + private static class InputFileChangesWrapper implements InputFileChanges { + private final InputFileChanges inputFileChangesDelegate; + private final ChangeContainer changeContainerDelegate; + + public InputFileChangesWrapper(InputFileChanges inputFileChangesDelegate, ChangeContainer changeContainerDelegate) { + this.inputFileChangesDelegate = inputFileChangesDelegate; + this.changeContainerDelegate = changeContainerDelegate; + } + + @Override + public boolean accept(String propertyName, ChangeVisitor visitor) { + return inputFileChangesDelegate.accept(propertyName, visitor); + } + + @Override + public boolean accept(ChangeVisitor visitor) { + return changeContainerDelegate.accept(visitor); + } + } + + private static class IncrementalDetectedExecutionStateChanges extends AbstractDetectedExecutionStateChanges { + private final InputFileChanges inputFileChanges; + + public IncrementalDetectedExecutionStateChanges( + ImmutableList allChangeMessages, + ImmutableSortedMap inputFileProperties, + InputFileChanges incrementalInputFileChanges, + IncrementalInputProperties incrementalInputProperties + ) { + super(allChangeMessages, inputFileProperties, incrementalInputProperties); + this.inputFileChanges = incrementalInputFileChanges; + } + + @Override + public InputChangesInternal createInputChanges() { + return new IncrementalInputChanges(inputFileChanges, incrementalInputProperties); + } + } + + private static class NonIncrementalDetectedExecutionStateChanges extends AbstractDetectedExecutionStateChanges { + + public NonIncrementalDetectedExecutionStateChanges( + ImmutableList allChangeMessages, + ImmutableSortedMap inputFileProperties, + IncrementalInputProperties incrementalInputProperties + ) { + super(allChangeMessages, inputFileProperties, incrementalInputProperties); + } + + @Override + public InputChangesInternal createInputChanges() { + return new NonIncrementalInputChanges(inputFileProperties, incrementalInputProperties); + } + } + + private static abstract class AbstractDetectedExecutionStateChanges implements ExecutionStateChanges { + private final ImmutableList allChangeMessages; + protected final ImmutableSortedMap inputFileProperties; + protected final IncrementalInputProperties incrementalInputProperties; + + public AbstractDetectedExecutionStateChanges( + ImmutableList allChangeMessages, + ImmutableSortedMap incrementalInputFileProperties, IncrementalInputProperties incrementalInputProperties) { + this.allChangeMessages = allChangeMessages; + this.inputFileProperties = incrementalInputFileProperties; + this.incrementalInputProperties = incrementalInputProperties; + } + + @Override + public ImmutableList getAllChangeMessages() { + return allChangeMessages; + } + + @Override + public ExecutionStateChanges withEnforcedRebuild(String rebuildReason) { + return new RebuildExecutionStateChanges(rebuildReason, inputFileProperties, incrementalInputProperties); + } + } + + private static class MessageCollectingChangeVisitor implements ChangeVisitor { + private final ImmutableCollection.Builder messages; + private final int max; + private int count; + + public MessageCollectingChangeVisitor(ImmutableCollection.Builder messages, int max) { + this.messages = messages; + this.max = max; + } + + @Override + public boolean visitChange(Change change) { + messages.add(change.getMessage()); + return ++count < max; + } + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultExecutionStateChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultExecutionStateChanges.java deleted file mode 100644 index fce75c1c9383f..0000000000000 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultExecutionStateChanges.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.execution.history.changes; - -import org.gradle.api.Describable; -import org.gradle.internal.change.CachingChangeContainer; -import org.gradle.internal.change.Change; -import org.gradle.internal.change.ChangeContainer; -import org.gradle.internal.change.ChangeDetectorVisitor; -import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.CollectingChangeVisitor; -import org.gradle.internal.change.ErrorHandlingChangeContainer; -import org.gradle.internal.change.SummarizingChangeContainer; -import org.gradle.internal.execution.history.AfterPreviousExecutionState; -import org.gradle.internal.execution.history.BeforeExecutionState; - -public class DefaultExecutionStateChanges implements ExecutionStateChanges { - - private final AfterPreviousExecutionState previousExecution; - private final ChangeContainer inputFileChanges; - private final ChangeContainer allChanges; - private final ChangeContainer rebuildTriggeringChanges; - - public DefaultExecutionStateChanges(AfterPreviousExecutionState lastExecution, BeforeExecutionState thisExecution, Describable executable) { - this.previousExecution = lastExecution; - - // Capture changes in execution outcome - ChangeContainer previousSuccessState = new PreviousSuccessChanges( - lastExecution.isSuccessful()); - - // Capture changes to implementation - ChangeContainer implementationChanges = new ImplementationChanges( - lastExecution.getImplementation(), lastExecution.getAdditionalImplementations(), - thisExecution.getImplementation(), thisExecution.getAdditionalImplementations(), - executable); - - // Capture non-file input changes - ChangeContainer inputPropertyChanges = new PropertyChanges( - lastExecution.getInputProperties(), - thisExecution.getInputProperties(), - "Input", - executable); - ChangeContainer inputPropertyValueChanges = new InputValueChanges( - lastExecution.getInputProperties(), - thisExecution.getInputProperties(), - executable); - - // Capture input files state - ChangeContainer inputFilePropertyChanges = new PropertyChanges( - lastExecution.getInputFileProperties(), - thisExecution.getInputFileProperties(), - "Input file", - executable); - InputFileChanges directInputFileChanges = new InputFileChanges( - lastExecution.getInputFileProperties(), - thisExecution.getInputFileProperties()); - ChangeContainer inputFileChanges = caching(directInputFileChanges); - this.inputFileChanges = errorHandling(executable, inputFileChanges); - - // Capture output files state - ChangeContainer outputFilePropertyChanges = new PropertyChanges( - lastExecution.getOutputFileProperties(), - thisExecution.getOutputFileProperties(), - "Output", - executable); - OutputFileChanges uncachedOutputChanges = new OutputFileChanges( - lastExecution.getOutputFileProperties(), - thisExecution.getOutputFileProperties()); - ChangeContainer outputFileChanges = caching(uncachedOutputChanges); - - this.allChanges = errorHandling(executable, new SummarizingChangeContainer(previousSuccessState, implementationChanges, inputPropertyChanges, inputPropertyValueChanges, outputFilePropertyChanges, outputFileChanges, inputFilePropertyChanges, inputFileChanges)); - this.rebuildTriggeringChanges = errorHandling(executable, new SummarizingChangeContainer(previousSuccessState, implementationChanges, inputPropertyChanges, inputPropertyValueChanges, inputFilePropertyChanges, outputFilePropertyChanges, outputFileChanges)); - } - - private static ChangeContainer caching(ChangeContainer wrapped) { - return new CachingChangeContainer(MAX_OUT_OF_DATE_MESSAGES, wrapped); - } - - private static ChangeContainer errorHandling(Describable executable, ChangeContainer wrapped) { - return new ErrorHandlingChangeContainer(executable, wrapped); - } - - @Override - public Iterable getInputFilesChanges() { - CollectingChangeVisitor visitor = new CollectingChangeVisitor(); - inputFileChanges.accept(visitor); - return visitor.getChanges(); - } - - @Override - public void visitAllChanges(ChangeVisitor visitor) { - allChanges.accept(visitor); - } - - @Override - public boolean isRebuildRequired() { - ChangeDetectorVisitor changeDetectorVisitor = new ChangeDetectorVisitor(); - rebuildTriggeringChanges.accept(changeDetectorVisitor); - return changeDetectorVisitor.hasAnyChanges(); - } - - @Override - public AfterPreviousExecutionState getPreviousExecution() { - return previousExecution; - } -} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultIncrementalInputProperties.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultIncrementalInputProperties.java new file mode 100644 index 0000000000000..d5cf478dc7bb0 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultIncrementalInputProperties.java @@ -0,0 +1,59 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableSortedMap; +import com.google.common.collect.Maps; +import org.gradle.api.InvalidUserDataException; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.fingerprint.FileCollectionFingerprint; + +public class DefaultIncrementalInputProperties implements IncrementalInputProperties { + private final ImmutableBiMap incrementalInputProperties; + + public DefaultIncrementalInputProperties(ImmutableBiMap incrementalInputProperties) { + this.incrementalInputProperties = incrementalInputProperties; + } + + @Override + public String getPropertyNameFor(Object propertyValue) { + String propertyName = incrementalInputProperties.inverse().get(propertyValue); + if (propertyName == null) { + throw new InvalidUserDataException("Cannot query incremental changes: No property found for value " + propertyValue + ". Incremental properties: " + Joiner.on(", ").join(incrementalInputProperties.keySet()) + "."); + } + return propertyName; + } + + @Override + public InputFileChanges nonIncrementalChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { + return new DefaultInputFileChanges( + Maps.filterKeys(previous, propertyName -> !incrementalInputProperties.containsKey(propertyName)), + Maps.filterKeys(current, propertyName -> !incrementalInputProperties.containsKey(propertyName)) + ); + + } + + @Override + public InputFileChanges incrementalChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { + return new DefaultInputFileChanges( + ImmutableSortedMap.copyOfSorted(Maps.filterKeys(previous, propertyName -> incrementalInputProperties.containsKey(propertyName))), + ImmutableSortedMap.copyOfSorted(Maps.filterKeys(current, propertyName -> incrementalInputProperties.containsKey(propertyName))) + ); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultInputFileChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultInputFileChanges.java new file mode 100644 index 0000000000000..ec4c1dadc6e96 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/DefaultInputFileChanges.java @@ -0,0 +1,43 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes; + +import org.gradle.internal.change.ChangeVisitor; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.fingerprint.FileCollectionFingerprint; + +import java.util.SortedMap; + +public class DefaultInputFileChanges extends AbstractFingerprintChanges implements InputFileChanges { + private static final String TITLE = "Input"; + + public DefaultInputFileChanges(SortedMap previous, SortedMap current) { + super(previous, current, TITLE); + } + + @Override + public boolean accept(ChangeVisitor visitor) { + return accept(visitor, true); + } + + @Override + public boolean accept(String propertyName, ChangeVisitor visitor) { + CurrentFileCollectionFingerprint currentFileCollectionFingerprint = current.get(propertyName); + FileCollectionFingerprint previousFileCollectionFingerprint = previous.get(propertyName); + return currentFileCollectionFingerprint.visitChangesSince(previousFileCollectionFingerprint, TITLE, true, visitor); + } +} diff --git a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskCacheKeyCalculator.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/ExecutionStateChangeDetector.java similarity index 52% rename from subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskCacheKeyCalculator.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/ExecutionStateChangeDetector.java index 32cfc06b9b651..85187762613af 100644 --- a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskCacheKeyCalculator.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/ExecutionStateChangeDetector.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,19 @@ * limitations under the License. */ -package org.gradle.caching.internal.tasks; +package org.gradle.internal.execution.history.changes; -import org.gradle.api.internal.TaskInternal; -import org.gradle.api.internal.tasks.properties.TaskProperties; +import org.gradle.api.Describable; +import org.gradle.internal.execution.history.AfterPreviousExecutionState; import org.gradle.internal.execution.history.BeforeExecutionState; -public interface TaskCacheKeyCalculator { - TaskOutputCachingBuildCacheKey calculate(TaskInternal task, BeforeExecutionState execution, TaskProperties properties, boolean buildCacheDebugLogging); +public interface ExecutionStateChangeDetector { + int MAX_OUT_OF_DATE_MESSAGES = 3; + + ExecutionStateChanges detectChanges( + AfterPreviousExecutionState lastExecution, + BeforeExecutionState thisExecution, + Describable executable, + boolean allowOverlappingOutputs, + IncrementalInputProperties incrementalInputProperties); } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/ExecutionStateChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/ExecutionStateChanges.java index ca6eb025164f0..3200a18b6f02f 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/ExecutionStateChanges.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/ExecutionStateChanges.java @@ -16,34 +16,22 @@ package org.gradle.internal.execution.history.changes; -import org.gradle.internal.change.Change; -import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import com.google.common.collect.ImmutableList; /** * Represents the complete changes in execution state */ public interface ExecutionStateChanges { - int MAX_OUT_OF_DATE_MESSAGES = 3; - - /** - * Returns changes to input files only. - */ - Iterable getInputFilesChanges(); - /** - * Visits any change to inputs or outputs. + * Returns all change messages for inputs and outputs. */ - void visitAllChanges(ChangeVisitor visitor); + ImmutableList getAllChangeMessages(); - /** - * Whether there are changes that force an incremental task to fully rebuild. - */ - boolean isRebuildRequired(); + InputChangesInternal createInputChanges(); /** - * The base execution the changes are calculated against. + * Turn these changes into ones forcing a rebuild with the given reason. */ - AfterPreviousExecutionState getPreviousExecution(); + ExecutionStateChanges withEnforcedRebuild(String rebuildReason); } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/IncrementalInputChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/IncrementalInputChanges.java new file mode 100644 index 0000000000000..899fa7248e15d --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/IncrementalInputChanges.java @@ -0,0 +1,65 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes; + +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.incremental.InputFileDetails; +import org.gradle.internal.Cast; +import org.gradle.internal.change.CollectingChangeVisitor; +import org.gradle.work.FileChange; + +public class IncrementalInputChanges implements InputChangesInternal { + + private final InputFileChanges changes; + private final IncrementalInputProperties incrementalInputProperties; + + public IncrementalInputChanges(InputFileChanges changes, IncrementalInputProperties incrementalInputProperties) { + this.changes = changes; + this.incrementalInputProperties = incrementalInputProperties; + } + + @Override + public boolean isIncremental() { + return true; + } + + @Override + public Iterable getFileChanges(FileCollection parameter) { + return getObjectFileChanges(parameter); + } + + @Override + public Iterable getFileChanges(Provider parameter) { + return getObjectFileChanges(parameter); + } + + private Iterable getObjectFileChanges(Object parameter) { + String propertyName = incrementalInputProperties.getPropertyNameFor(parameter); + CollectingChangeVisitor visitor = new CollectingChangeVisitor(); + changes.accept(propertyName, visitor); + return Cast.uncheckedNonnullCast(visitor.getChanges()); + } + + @Override + public Iterable getAllFileChanges() { + CollectingChangeVisitor visitor = new CollectingChangeVisitor(); + changes.accept(visitor); + return Cast.uncheckedNonnullCast(visitor.getChanges()); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/IncrementalInputProperties.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/IncrementalInputProperties.java new file mode 100644 index 0000000000000..08d6083ba6c90 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/IncrementalInputProperties.java @@ -0,0 +1,62 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes; + +import com.google.common.collect.ImmutableSortedMap; +import org.gradle.api.InvalidUserDataException; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.fingerprint.FileCollectionFingerprint; + +public interface IncrementalInputProperties { + String getPropertyNameFor(Object value); + InputFileChanges nonIncrementalChanges(ImmutableSortedMap previous, ImmutableSortedMap current); + InputFileChanges incrementalChanges(ImmutableSortedMap previous, ImmutableSortedMap current); + + IncrementalInputProperties NONE = new IncrementalInputProperties() { + @Override + public String getPropertyNameFor(Object value) { + throw new InvalidUserDataException("Cannot query incremental changes for property " + value + ": No incremental properties declared."); + } + + @Override + public InputFileChanges nonIncrementalChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { + return new DefaultInputFileChanges(previous, current); + } + + @Override + public InputFileChanges incrementalChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { + return InputFileChanges.EMPTY; + } + }; + + IncrementalInputProperties ALL = new IncrementalInputProperties() { + @Override + public String getPropertyNameFor(Object value) { + throw new InvalidUserDataException("Cannot query incremental changes for property " + value + ": Requires using 'InputChanges'."); + } + + @Override + public InputFileChanges nonIncrementalChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { + return InputFileChanges.EMPTY; + } + + @Override + public InputFileChanges incrementalChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { + return new DefaultInputFileChanges(previous, current); + } + }; +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/InputChangesInternal.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/InputChangesInternal.java new file mode 100644 index 0000000000000..896e73265359d --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/InputChangesInternal.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes; + +import org.gradle.api.tasks.incremental.InputFileDetails; +import org.gradle.work.InputChanges; + +public interface InputChangesInternal extends InputChanges { + Iterable getAllFileChanges(); +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/InputFileChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/InputFileChanges.java index 5fbe400376724..f18fc52edffe7 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/InputFileChanges.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/InputFileChanges.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +16,23 @@ package org.gradle.internal.execution.history.changes; -import com.google.common.collect.ImmutableSortedMap; +import org.gradle.api.InvalidUserDataException; +import org.gradle.internal.change.ChangeContainer; import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.gradle.internal.fingerprint.FileCollectionFingerprint; -public class InputFileChanges extends AbstractFingerprintChanges { +public interface InputFileChanges extends ChangeContainer { + boolean accept(String propertyName, ChangeVisitor visitor); - public InputFileChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { - super(previous, current, "Input"); - } + InputFileChanges EMPTY = new InputFileChanges() { - @Override - public boolean accept(ChangeVisitor visitor) { - return accept(visitor, true); - } + @Override + public boolean accept(ChangeVisitor visitor) { + return true; + } + + @Override + public boolean accept(String propertyName, ChangeVisitor visitor) { + throw new InvalidUserDataException("Cannot query incremental changes for property " + propertyName + ": No incremental properties declared."); + } + }; } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/NoHistoryTaskUpToDateState.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/NoHistoryTaskUpToDateState.java deleted file mode 100644 index 565869bb82e60..0000000000000 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/NoHistoryTaskUpToDateState.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.execution.history.changes; - -import org.gradle.internal.change.Change; -import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.DescriptiveChange; -import org.gradle.internal.execution.history.AfterPreviousExecutionState; - -public class NoHistoryTaskUpToDateState implements ExecutionStateChanges { - - public static final NoHistoryTaskUpToDateState INSTANCE = new NoHistoryTaskUpToDateState(); - - private final DescriptiveChange noHistoryChange = new DescriptiveChange("No history is available."); - - @Override - public Iterable getInputFilesChanges() { - throw new UnsupportedOperationException("Input file changes can only be queried when history is available."); - } - - @Override - public void visitAllChanges(ChangeVisitor visitor) { - visitor.visitChange(noHistoryChange); - } - - @Override - public boolean isRebuildRequired() { - return true; - } - - @Override - public AfterPreviousExecutionState getPreviousExecution() { - throw new UnsupportedOperationException(); - } -} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/NonIncrementalInputChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/NonIncrementalInputChanges.java new file mode 100644 index 0000000000000..e3a174785193e --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/NonIncrementalInputChanges.java @@ -0,0 +1,143 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes; + +import com.google.common.collect.ImmutableSortedMap; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.FileSystemLocation; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.incremental.InputFileDetails; +import org.gradle.internal.Cast; +import org.gradle.internal.file.FileType; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.work.ChangeType; +import org.gradle.work.FileChange; + +import java.io.File; +import java.util.Objects; +import java.util.stream.Stream; + +public class NonIncrementalInputChanges implements InputChangesInternal { + private final ImmutableSortedMap currentInputs; + private final IncrementalInputProperties incrementalInputProperties; + + public NonIncrementalInputChanges(ImmutableSortedMap currentInputs, IncrementalInputProperties incrementalInputProperties) { + this.currentInputs = currentInputs; + this.incrementalInputProperties = incrementalInputProperties; + } + + @Override + public boolean isIncremental() { + return false; + } + + @Override + public Iterable getFileChanges(FileCollection parameter) { + return getObjectFileChanges(parameter); + } + + @Override + public Iterable getFileChanges(Provider parameter) { + return getObjectFileChanges(parameter); + } + + public Iterable getObjectFileChanges(Object parameter) { + CurrentFileCollectionFingerprint currentFileCollectionFingerprint = currentInputs.get(incrementalInputProperties.getPropertyNameFor(parameter)); + return () -> getAllFileChanges(currentFileCollectionFingerprint).iterator(); + } + + @Override + public Iterable getAllFileChanges() { + Iterable changes = () -> currentInputs.values().stream().flatMap(NonIncrementalInputChanges::getAllFileChanges).iterator(); + return Cast.uncheckedNonnullCast(changes); + } + + private static Stream getAllFileChanges(CurrentFileCollectionFingerprint currentFileCollectionFingerprint) { + return currentFileCollectionFingerprint.getFingerprints().entrySet().stream() + .map(entry -> new RebuildFileChange(entry.getKey(), entry.getValue().getNormalizedPath(), entry.getValue().getType())); + } + + private static class RebuildFileChange implements FileChange, InputFileDetails { + private final String path; + private final String normalizedPath; + private final FileType fileType; + + public RebuildFileChange(String path, String normalizedPath, FileType fileType) { + this.path = path; + this.normalizedPath = normalizedPath; + this.fileType = fileType; + } + + @Override + public File getFile() { + return new File(path); + } + + @Override + public ChangeType getChangeType() { + return ChangeType.ADDED; + } + + @Override + public org.gradle.api.file.FileType getFileType() { + return fileType.toPublicType(); + } + + @Override + public String getNormalizedPath() { + return normalizedPath; + } + + @Override + public boolean isAdded() { + return false; + } + + @Override + public boolean isModified() { + return false; + } + + @Override + public boolean isRemoved() { + return false; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RebuildFileChange that = (RebuildFileChange) o; + return path.equals(that.path) && + normalizedPath.equals(that.normalizedPath); + } + + @Override + public int hashCode() { + return Objects.hash(path, normalizedPath); + } + + @Override + public String toString() { + return "Input file " + path + " added for rebuild."; + } + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/OutputFileChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/OutputFileChanges.java index cfba7cc5e5d7a..0855de65e5ca1 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/OutputFileChanges.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/OutputFileChanges.java @@ -17,25 +17,21 @@ package org.gradle.internal.execution.history.changes; import com.google.common.collect.ImmutableSortedMap; -import org.gradle.internal.change.ChangeDetectorVisitor; import org.gradle.internal.change.ChangeVisitor; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; import org.gradle.internal.fingerprint.FileCollectionFingerprint; public class OutputFileChanges extends AbstractFingerprintChanges { - public OutputFileChanges(ImmutableSortedMap previous, ImmutableSortedMap current) { - super(previous, current, "Output"); - } + private final boolean includeAdded; - public boolean hasAnyChanges() { - ChangeDetectorVisitor changeDetectorVisitor = new ChangeDetectorVisitor(); - accept(changeDetectorVisitor, true); - return changeDetectorVisitor.hasAnyChanges(); + public OutputFileChanges(ImmutableSortedMap previous, ImmutableSortedMap current, boolean includeAdded) { + super(previous, current, "Output"); + this.includeAdded = includeAdded; } @Override public boolean accept(ChangeVisitor visitor) { - return accept(visitor, false); + return accept(visitor, includeAdded); } } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/RebuildExecutionStateChanges.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/RebuildExecutionStateChanges.java new file mode 100644 index 0000000000000..5a4cfe11a6aa8 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/history/changes/RebuildExecutionStateChanges.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; + +import javax.annotation.Nullable; + +public class RebuildExecutionStateChanges implements ExecutionStateChanges { + private final String rebuildReason; + private final ImmutableSortedMap inputFileProperties; + private final IncrementalInputProperties incrementalInputProperties; + + public RebuildExecutionStateChanges( + String rebuildReason, + @Nullable ImmutableSortedMap inputFileProperties, + IncrementalInputProperties incrementalInputProperties + ) { + this.rebuildReason = rebuildReason; + this.inputFileProperties = inputFileProperties; + this.incrementalInputProperties = incrementalInputProperties; + } + + @Override + public ImmutableList getAllChangeMessages() { + return ImmutableList.of(rebuildReason); + } + + @Override + public InputChangesInternal createInputChanges() { + if (inputFileProperties == null) { + throw new UnsupportedOperationException("Cannot query input changes when input tracking is disabled."); + } + return new NonIncrementalInputChanges(inputFileProperties, incrementalInputProperties); + } + + @Override + public ExecutionStateChanges withEnforcedRebuild(String rebuildReason) { + return new RebuildExecutionStateChanges(rebuildReason, inputFileProperties, incrementalInputProperties); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/DefaultWorkExecutor.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/DefaultWorkExecutor.java index 2ff83f3e6d4ae..445840581333e 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/DefaultWorkExecutor.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/DefaultWorkExecutor.java @@ -16,26 +16,20 @@ package org.gradle.internal.execution.impl; +import org.gradle.internal.execution.Context; import org.gradle.internal.execution.Result; -import org.gradle.internal.execution.UnitOfWork; +import org.gradle.internal.execution.Step; import org.gradle.internal.execution.WorkExecutor; -import org.gradle.internal.execution.impl.steps.Context; -import org.gradle.internal.execution.impl.steps.Step; -public class DefaultWorkExecutor implements WorkExecutor { - private final Step executeStep; +public class DefaultWorkExecutor implements WorkExecutor { + private final Step executeStep; - public DefaultWorkExecutor(Step executeStep) { + public DefaultWorkExecutor(Step executeStep) { this.executeStep = executeStep; } @Override - public R execute(UnitOfWork work) { - return executeStep.execute(new Context() { - @Override - public UnitOfWork getWork() { - return work; - } - }); + public R execute(C context) { + return executeStep.execute(context); } } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CacheStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CacheStep.java deleted file mode 100644 index 210fb78e94354..0000000000000 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CacheStep.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.execution.impl.steps; - -import com.google.common.collect.ImmutableSortedMap; -import org.gradle.caching.BuildCacheKey; -import org.gradle.caching.internal.command.BuildCacheCommandFactory; -import org.gradle.caching.internal.command.BuildCacheLoadListener; -import org.gradle.caching.internal.controller.BuildCacheController; -import org.gradle.caching.internal.origin.OriginMetadata; -import org.gradle.caching.internal.packaging.UnrecoverableUnpackingException; -import org.gradle.internal.Try; -import org.gradle.internal.execution.ExecutionOutcome; -import org.gradle.internal.execution.OutputChangeListener; -import org.gradle.internal.execution.UnitOfWork; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import java.util.Optional; - -public class CacheStep implements Step { - private static final Logger LOGGER = LoggerFactory.getLogger(CacheStep.class); - - private final BuildCacheController buildCache; - private final OutputChangeListener outputChangeListener; - private final BuildCacheCommandFactory commandFactory; - private final Step delegate; - - public CacheStep( - BuildCacheController buildCache, - OutputChangeListener outputChangeListener, - BuildCacheCommandFactory commandFactory, - Step delegate - ) { - this.buildCache = buildCache; - this.outputChangeListener = outputChangeListener; - this.commandFactory = commandFactory; - this.delegate = delegate; - } - - @Override - public CurrentSnapshotResult execute(C context) { - if (!buildCache.isEnabled()) { - return executeWithoutCache(context); - } - return context.getCacheHandler() - .load(cacheKey -> load(context.getWork(), cacheKey)) - .map(loadResult -> (CurrentSnapshotResult) new CurrentSnapshotResult() { - @Override - public Try getOutcome() { - return Try.successful(ExecutionOutcome.FROM_CACHE); - } - - @Override - public OriginMetadata getOriginMetadata() { - return loadResult.getOriginMetadata(); - } - - @Override - public boolean isReused() { - return true; - } - - @Override - public ImmutableSortedMap getFinalOutputs() { - return loadResult.getResultingSnapshots(); - } - }) - .orElseGet(() -> { - CurrentSnapshotResult executionResult = executeWithoutCache(context); - executionResult.getOutcome().ifSuccessfulOrElse( - outcome -> context.getCacheHandler().store(cacheKey -> store(context.getWork(), cacheKey, executionResult)), - failure -> LOGGER.debug("Not storing result of {} in cache because the execution failed", context.getWork().getDisplayName()) - ); - return executionResult; - }); - } - - @Nullable - private BuildCacheCommandFactory.LoadMetadata load(UnitOfWork work, BuildCacheKey cacheKey) { - try { - return buildCache.load( - commandFactory.createLoad(cacheKey, work, new BuildCacheLoadListener() { - @Override - public void beforeLoad() { - Optional> changingOutputs = work.getChangingOutputs(); - changingOutputs.ifPresent(affectedPaths -> outputChangeListener.beforeOutputChange(affectedPaths)); - if (!changingOutputs.isPresent()) { - outputChangeListener.beforeOutputChange(); - } - } - - @Override - public void afterLoadFailedAndWasCleanedUp(Throwable error) { - work.outputsRemovedAfterFailureToLoadFromCache(); - } - }) - ); - } catch (UnrecoverableUnpackingException e) { - // We didn't manage to recover from the unpacking error, there might be leftover - // garbage among the task's outputs, thus we must fail the build - throw e; - } catch (Exception e) { - // There was a failure during downloading, previous task outputs should be unaffected - LOGGER.warn("Failed to load cache entry for {}, falling back to executing task", work.getDisplayName(), e); - return null; - } - } - - private void store(UnitOfWork work, BuildCacheKey cacheKey, CurrentSnapshotResult result) { - try { - // TODO This could send in the whole origin metadata - buildCache.store(commandFactory.createStore(cacheKey, work, result.getFinalOutputs(), result.getOriginMetadata().getExecutionTime())); - } catch (Exception e) { - LOGGER.warn("Failed to store cache entry {}", cacheKey.getDisplayName(), e); - } - } - - private CurrentSnapshotResult executeWithoutCache(C context) { - return delegate.execute(context); - } -} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/PrepareCachingStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/PrepareCachingStep.java deleted file mode 100644 index 49a18f75201eb..0000000000000 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/PrepareCachingStep.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.execution.impl.steps; - -import org.gradle.internal.execution.CacheHandler; -import org.gradle.internal.execution.Result; -import org.gradle.internal.execution.UnitOfWork; - -public class PrepareCachingStep implements Step { - private final Step delegate; - - public PrepareCachingStep(Step delegate) { - this.delegate = delegate; - } - - @Override - public R execute(C context) { - CacheHandler cacheHandler = context.getWork().createCacheHandler(); - return delegate.execute(new CachingContext() { - @Override - public CacheHandler getCacheHandler() { - return cacheHandler; - } - - @Override - public UnitOfWork getWork() { - return context.getWork(); - } - }); - } -} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/ExecuteStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/BroadcastChangingOutputsStep.java similarity index 70% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/ExecuteStep.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/BroadcastChangingOutputsStep.java index 96808723a17fb..8e390cc4cd55c 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/ExecuteStep.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/BroadcastChangingOutputsStep.java @@ -14,28 +14,31 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; -import org.gradle.internal.Try; -import org.gradle.internal.execution.ExecutionOutcome; +import org.gradle.internal.execution.Context; import org.gradle.internal.execution.OutputChangeListener; import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; import org.gradle.internal.execution.UnitOfWork; import java.util.Optional; -public class ExecuteStep implements Step { +public class BroadcastChangingOutputsStep implements Step { private final OutputChangeListener outputChangeListener; + private final Step delegate; - public ExecuteStep( - OutputChangeListener outputChangeListener + public BroadcastChangingOutputsStep( + OutputChangeListener outputChangeListener, + Step delegate ) { this.outputChangeListener = outputChangeListener; + this.delegate = delegate; } @Override - public Result execute(Context context) { + public R execute(C context) { UnitOfWork work = context.getWork(); Optional> changingOutputs = work.getChangingOutputs(); @@ -43,12 +46,6 @@ public Result execute(Context context) { if (!changingOutputs.isPresent()) { outputChangeListener.beforeOutputChange(); } - ExecutionOutcome outcome = work.execute(); - return new Result() { - @Override - public Try getOutcome() { - return Try.successful(outcome); - } - }; + return delegate.execute(context); } } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CacheStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CacheStep.java new file mode 100644 index 0000000000000..763832cacd665 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CacheStep.java @@ -0,0 +1,203 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps; + +import com.google.common.collect.ImmutableSortedMap; +import org.apache.commons.io.FileUtils; +import org.gradle.api.UncheckedIOException; +import org.gradle.caching.BuildCacheKey; +import org.gradle.caching.internal.command.BuildCacheCommandFactory; +import org.gradle.caching.internal.command.BuildCacheCommandFactory.LoadMetadata; +import org.gradle.caching.internal.controller.BuildCacheController; +import org.gradle.caching.internal.origin.OriginMetadata; +import org.gradle.internal.Try; +import org.gradle.internal.execution.CurrentSnapshotResult; +import org.gradle.internal.execution.ExecutionOutcome; +import org.gradle.internal.execution.IncrementalChangesContext; +import org.gradle.internal.execution.Step; +import org.gradle.internal.execution.UnitOfWork; +import org.gradle.internal.execution.caching.CachingState; +import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import org.gradle.internal.execution.history.BeforeExecutionState; +import org.gradle.internal.execution.history.changes.ExecutionStateChanges; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; + +public class CacheStep implements Step { + private static final Logger LOGGER = LoggerFactory.getLogger(CacheStep.class); + + private static final String FAILED_LOAD_REBUILD_REASON = "Outputs removed due to failed load from cache"; + + private final BuildCacheController buildCache; + private final BuildCacheCommandFactory commandFactory; + private final Step delegate; + + public CacheStep( + BuildCacheController buildCache, + BuildCacheCommandFactory commandFactory, + Step delegate + ) { + this.buildCache = buildCache; + this.commandFactory = commandFactory; + this.delegate = delegate; + } + + @Override + public CurrentSnapshotResult execute(IncrementalChangesContext context) { + CachingState cachingState = context.getCachingState(); + //noinspection OptionalGetWithoutIsPresent + return cachingState.getDisabledReasons().isEmpty() + ? executeWithCache(context, cachingState.getKey().get()) + : executeWithoutCache(context); + } + + private CurrentSnapshotResult executeWithCache(IncrementalChangesContext context, BuildCacheKey cacheKey) { + UnitOfWork work = context.getWork(); + return Try.ofFailable(() -> work.isAllowedToLoadFromCache() + ? buildCache.load(commandFactory.createLoad(cacheKey, work)) + : Optional.empty() + ) + .map(successfulLoad -> successfulLoad + .map(cacheHit -> { + cleanLocalState(work); + OriginMetadata originMetadata = cacheHit.getOriginMetadata(); + ImmutableSortedMap finalOutputs = cacheHit.getResultingSnapshots(); + return (CurrentSnapshotResult) new CurrentSnapshotResult() { + @Override + public Try getOutcome() { + return Try.successful(ExecutionOutcome.FROM_CACHE); + } + + @Override + public OriginMetadata getOriginMetadata() { + return originMetadata; + } + + @Override + public boolean isReused() { + return true; + } + + @Override + public ImmutableSortedMap getFinalOutputs() { + return finalOutputs; + } + }; + }) + .orElseGet(() -> executeAndStoreInCache(cacheKey, context)) + ) + .orElseMapFailure(loadFailure -> { + LOGGER.warn("Failed to load cache entry for {}, cleaning outputs and falling back to (non-incremental) execution", + work.getDisplayName(), loadFailure); + + cleanLocalState(work); + cleanOutputsAfterLoadFailure(work); + Optional rebuildChanges = context.getChanges().map(changes -> changes.withEnforcedRebuild(FAILED_LOAD_REBUILD_REASON)); + + return executeAndStoreInCache(cacheKey, new IncrementalChangesContext() { + @Override + public Optional getChanges() { + return rebuildChanges; + } + + @Override + public CachingState getCachingState() { + return context.getCachingState(); + } + + @Override + public Optional getRebuildReason() { + return Optional.of(FAILED_LOAD_REBUILD_REASON); + } + + @Override + public Optional getAfterPreviousExecutionState() { + return context.getAfterPreviousExecutionState(); + } + + @Override + public Optional getBeforeExecutionState() { + return context.getBeforeExecutionState(); + } + + @Override + public UnitOfWork getWork() { + return work; + } + }); + }); + } + + private static void cleanLocalState(UnitOfWork work) { + work.visitLocalState(localStateFile -> { + try { + remove(localStateFile); + } catch (IOException ex) { + throw new UncheckedIOException(String.format("Failed to clean up local state files for %s: %s", work.getDisplayName(), localStateFile), ex); + } + }); + } + + private static void cleanOutputsAfterLoadFailure(UnitOfWork work) { + work.visitOutputProperties((name, type, roots) -> { + for (File root : roots) { + try { + remove(root); + } catch (IOException ex) { + throw new UncheckedIOException(String.format("Failed to clean up files for tree '%s' of %s: %s", name, work.getDisplayName(), root), ex); + } + } + }); + } + + private static void remove(File root) throws IOException { + if (root.exists()) { + if (root.isDirectory()) { + FileUtils.cleanDirectory(root); + } else { + FileUtils.forceDelete(root); + } + } + } + + private CurrentSnapshotResult executeAndStoreInCache(BuildCacheKey cacheKey, IncrementalChangesContext context) { + CurrentSnapshotResult executionResult = executeWithoutCache(context); + executionResult.getOutcome().ifSuccessfulOrElse( + outcome -> store(context.getWork(), cacheKey, executionResult), + failure -> LOGGER.debug("Not storing result of {} in cache because the execution failed", context.getWork().getDisplayName()) + ); + return executionResult; + } + + private void store(UnitOfWork work, BuildCacheKey cacheKey, CurrentSnapshotResult result) { + try { + // TODO This could send in the whole origin metadata + buildCache.store(commandFactory.createStore(cacheKey, work, result.getFinalOutputs(), result.getOriginMetadata().getExecutionTime())); + } catch (Exception e) { + LOGGER.warn("Failed to store cache entry {}", cacheKey.getDisplayName(), e); + } + } + + private CurrentSnapshotResult executeWithoutCache(IncrementalChangesContext context) { + return delegate.execute(context); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CancelExecutionStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CancelExecutionStep.java similarity index 83% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CancelExecutionStep.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CancelExecutionStep.java index b29476481444c..46599b3df9d41 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CancelExecutionStep.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CancelExecutionStep.java @@ -14,17 +14,22 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; import org.gradle.api.BuildCancelledException; import org.gradle.initialization.BuildCancellationToken; +import org.gradle.internal.execution.Context; import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; public class CancelExecutionStep implements Step { private final BuildCancellationToken cancellationToken; private final Step delegate; - public CancelExecutionStep(BuildCancellationToken cancellationToken, Step delegate) { + public CancelExecutionStep( + BuildCancellationToken cancellationToken, + Step delegate + ) { this.cancellationToken = cancellationToken; this.delegate = delegate; } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CatchExceptionStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CatchExceptionStep.java similarity index 81% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CatchExceptionStep.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CatchExceptionStep.java index edb7bcb9dee9e..f54376950a597 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CatchExceptionStep.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CatchExceptionStep.java @@ -14,15 +14,16 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; import org.gradle.internal.Try; -import org.gradle.internal.execution.ExecutionException; +import org.gradle.internal.execution.Context; import org.gradle.internal.execution.ExecutionOutcome; import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; public class CatchExceptionStep implements Step { - private final Step delegate; + private final Step delegate; public CatchExceptionStep(Step delegate) { this.delegate = delegate; @@ -32,8 +33,7 @@ public CatchExceptionStep(Step delegate) { public Result execute(C context) { try { return delegate.execute(context); - } catch (Throwable t) { - ExecutionException failure = new ExecutionException(context.getWork(), t); + } catch (Throwable failure) { return new Result() { @Override public Try getOutcome() { diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CreateOutputsStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CreateOutputsStep.java similarity index 94% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CreateOutputsStep.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CreateOutputsStep.java index 3607f480bda5d..28b1f77578434 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/CreateOutputsStep.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/CreateOutputsStep.java @@ -14,10 +14,12 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; import org.gradle.api.file.FileCollection; +import org.gradle.internal.execution.Context; import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; import org.gradle.internal.execution.UnitOfWork; import org.gradle.internal.file.TreeType; import org.slf4j.Logger; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ExecuteStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ExecuteStep.java new file mode 100644 index 0000000000000..e18077feddce4 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ExecuteStep.java @@ -0,0 +1,72 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps; + +import org.gradle.internal.Try; +import org.gradle.internal.execution.ExecutionOutcome; +import org.gradle.internal.execution.IncrementalChangesContext; +import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; +import org.gradle.internal.execution.UnitOfWork; +import org.gradle.internal.execution.history.changes.InputChangesInternal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ExecuteStep implements Step { + private static final Logger LOGGER = LoggerFactory.getLogger(ExecuteStep.class); + + @Override + public Result execute(C context) { + UnitOfWork work = context.getWork(); + InputChangesInternal inputChanges = work.isRequiresInputChanges() + ? determineInputChanges(work, context) + : null; + + boolean incremental = inputChanges != null && inputChanges.isIncremental(); + UnitOfWork.WorkResult result = work.execute(inputChanges); + ExecutionOutcome outcome = determineOutcome(result, incremental); + return new Result() { + @Override + public Try getOutcome() { + return Try.successful(outcome); + } + }; + } + + private ExecutionOutcome determineOutcome(UnitOfWork.WorkResult result, boolean incremental) { + switch (result) { + case DID_NO_WORK: + return ExecutionOutcome.UP_TO_DATE; + case DID_WORK: + return incremental ? ExecutionOutcome.EXECUTED_INCREMENTALLY : ExecutionOutcome.EXECUTED_NON_INCREMENTALLY; + default: + throw new IllegalArgumentException("Unknown result: " + result); + } + } + + private InputChangesInternal determineInputChanges(UnitOfWork work, IncrementalChangesContext context) { + return context.getChanges() + .map(changes -> { + InputChangesInternal inputChanges = changes.createInputChanges(); + if (!inputChanges.isIncremental()) { + LOGGER.info("All input files are considered out-of-date for incremental {}.", work.getDisplayName()); + } + return inputChanges; + }) + .orElseThrow(() -> new UnsupportedOperationException("Cannot use input changes when input tracking is disabled.")); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/StoreSnapshotsStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/RecordOutputsStep.java similarity index 60% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/StoreSnapshotsStep.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/RecordOutputsStep.java index 498dc78d4d838..ecb4b6ff8f313 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/StoreSnapshotsStep.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/RecordOutputsStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,17 +14,18 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; -import com.google.common.collect.ImmutableSortedMap; +import org.gradle.internal.execution.Context; +import org.gradle.internal.execution.CurrentSnapshotResult; +import org.gradle.internal.execution.Step; import org.gradle.internal.execution.history.OutputFilesRepository; -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; -public class StoreSnapshotsStep implements Step { +public class RecordOutputsStep implements Step { private final OutputFilesRepository outputFilesRepository; private final Step delegate; - public StoreSnapshotsStep( + public RecordOutputsStep( OutputFilesRepository outputFilesRepository, Step delegate ) { @@ -33,17 +34,9 @@ public StoreSnapshotsStep( } @Override - // TODO Return a simple Result (that includes the origin metadata) here public CurrentSnapshotResult execute(C context) { CurrentSnapshotResult result = delegate.execute(context); - ImmutableSortedMap finalOutputs = result.getFinalOutputs(); - context.getWork().persistResult( - finalOutputs, - result.getOutcome().isSuccessful(), - result.getOriginMetadata() - ); - outputFilesRepository.recordOutputs(finalOutputs.values()); - + outputFilesRepository.recordOutputs(result.getFinalOutputs().values()); return result; } } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ResolveCachingStateStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ResolveCachingStateStep.java new file mode 100644 index 0000000000000..76e5e00d27262 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ResolveCachingStateStep.java @@ -0,0 +1,188 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSortedMap; +import org.gradle.caching.BuildCacheKey; +import org.gradle.caching.internal.controller.BuildCacheController; +import org.gradle.caching.internal.origin.OriginMetadata; +import org.gradle.internal.Try; +import org.gradle.internal.execution.CachingContext; +import org.gradle.internal.execution.CachingResult; +import org.gradle.internal.execution.ExecutionOutcome; +import org.gradle.internal.execution.IncrementalContext; +import org.gradle.internal.execution.Step; +import org.gradle.internal.execution.UnitOfWork; +import org.gradle.internal.execution.UpToDateResult; +import org.gradle.internal.execution.caching.CachingDisabledReason; +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory; +import org.gradle.internal.execution.caching.CachingState; +import org.gradle.internal.execution.caching.CachingStateBuilder; +import org.gradle.internal.execution.caching.impl.DefaultCachingStateBuilder; +import org.gradle.internal.execution.caching.impl.LoggingCachingStateBuilder; +import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import org.gradle.internal.execution.history.BeforeExecutionState; +import org.gradle.internal.fingerprint.FileCollectionFingerprint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Formatter; +import java.util.List; +import java.util.Optional; + +public class ResolveCachingStateStep implements Step { + private static final Logger LOGGER = LoggerFactory.getLogger(ResolveCachingStateStep.class); + private static final CachingDisabledReason BUILD_CACHE_DISABLED_REASON = new CachingDisabledReason(CachingDisabledReasonCategory.BUILD_CACHE_DISABLED, "Build cache is disabled"); + private static final CachingState BUILD_CACHE_DISABLED_STATE = CachingState.disabledWithoutInputs(BUILD_CACHE_DISABLED_REASON); + + private final BuildCacheController buildCache; + private final boolean buildScansEnabled; + private final Step delegate; + + public ResolveCachingStateStep( + BuildCacheController buildCache, + boolean buildScansEnabled, + Step delegate + ) { + this.buildCache = buildCache; + this.buildScansEnabled = buildScansEnabled; + this.delegate = delegate; + } + + @Override + public CachingResult execute(IncrementalContext context) { + UnitOfWork work = context.getWork(); + CachingState cachingState; + if (!buildCache.isEnabled() && !buildScansEnabled) { + cachingState = BUILD_CACHE_DISABLED_STATE; + } else { + cachingState = context.getBeforeExecutionState() + .map(beforeExecutionState -> calculateCachingState(beforeExecutionState, work)) + .orElseGet(() -> (buildCache.isEnabled() ? work.shouldDisableCaching() : Optional.of(BUILD_CACHE_DISABLED_REASON)) + .map(disabledReason -> CachingState.disabledWithoutInputs(disabledReason)) + .orElse(CachingState.NOT_DETERMINED) + ); + } + + ImmutableList disabledReasons = cachingState.getDisabledReasons(); + if (disabledReasons.isEmpty()) { + //noinspection OptionalGetWithoutIsPresent + logCacheKey(cachingState.getKey().get(), work); + } else { + logDisabledReasons(disabledReasons, work); + } + + UpToDateResult result = delegate.execute(new CachingContext() { + @Override + public CachingState getCachingState() { + return cachingState; + } + + @Override + public Optional getRebuildReason() { + return context.getRebuildReason(); + } + + @Override + public Optional getAfterPreviousExecutionState() { + return context.getAfterPreviousExecutionState(); + } + + @Override + public Optional getBeforeExecutionState() { + return context.getBeforeExecutionState(); + } + + @Override + public UnitOfWork getWork() { + return work; + } + }); + return new CachingResult() { + @Override + public CachingState getCachingState() { + return cachingState; + } + + @Override + public ImmutableList getExecutionReasons() { + return result.getExecutionReasons(); + } + + @Override + public ImmutableSortedMap getFinalOutputs() { + return result.getFinalOutputs(); + } + + @Override + public OriginMetadata getOriginMetadata() { + return result.getOriginMetadata(); + } + + @Override + public boolean isReused() { + return result.isReused(); + } + + @Override + public Try getOutcome() { + return result.getOutcome(); + } + }; + } + + private CachingState calculateCachingState(BeforeExecutionState executionState, UnitOfWork work) { + CachingStateBuilder builder = buildCache.isEmitDebugLogging() + ? new LoggingCachingStateBuilder() + : new DefaultCachingStateBuilder(); + + if (!buildCache.isEnabled()) { + builder.markNotCacheable(BUILD_CACHE_DISABLED_REASON); + } + work.shouldDisableCaching().ifPresent(noCacheReason -> { + builder.markNotCacheable(noCacheReason); + }); + + builder.withImplementation(executionState.getImplementation()); + builder.withAdditionalImplementations(executionState.getAdditionalImplementations()); + builder.withInputValueFingerprints(executionState.getInputProperties()); + builder.withInputFilePropertyFingerprints(executionState.getInputFileProperties()); + builder.withOutputPropertyNames(executionState.getOutputFileProperties().keySet()); + + return builder.build(); + } + + private void logCacheKey(BuildCacheKey cacheKey, UnitOfWork work) { + if (buildCache.isEmitDebugLogging()) { + LOGGER.warn("Build cache key for {} is {}", work.getDisplayName(), cacheKey.getDisplayName()); + } else { + LOGGER.info("Build cache key for {} is {}", work.getDisplayName(), cacheKey.getDisplayName()); + } + } + + private void logDisabledReasons(List reasons, UnitOfWork work) { + if (LOGGER.isInfoEnabled()) { + Formatter formatter = new Formatter(); + formatter.format("Caching disabled for %s because:", work.getDisplayName()); + for (CachingDisabledReason reason : reasons) { + formatter.format("%n %s", reason.getMessage()); + } + LOGGER.info(formatter.toString()); + } + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ResolveChangesStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ResolveChangesStep.java new file mode 100644 index 0000000000000..867529ad6e183 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/ResolveChangesStep.java @@ -0,0 +1,129 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps; + +import com.google.common.collect.ImmutableBiMap; +import org.gradle.api.InvalidUserDataException; +import org.gradle.internal.execution.CachingContext; +import org.gradle.internal.execution.IncrementalChangesContext; +import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; +import org.gradle.internal.execution.UnitOfWork; +import org.gradle.internal.execution.caching.CachingState; +import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import org.gradle.internal.execution.history.BeforeExecutionState; +import org.gradle.internal.execution.history.changes.DefaultIncrementalInputProperties; +import org.gradle.internal.execution.history.changes.ExecutionStateChangeDetector; +import org.gradle.internal.execution.history.changes.ExecutionStateChanges; +import org.gradle.internal.execution.history.changes.IncrementalInputProperties; +import org.gradle.internal.execution.history.changes.RebuildExecutionStateChanges; + +import java.util.Optional; + +public class ResolveChangesStep implements Step { + private final ExecutionStateChangeDetector changeDetector; + private static final String NO_HISTORY = "No history is available."; + + private final Step delegate; + + public ResolveChangesStep( + ExecutionStateChangeDetector changeDetector, + Step delegate + ) { + this.changeDetector = changeDetector; + this.delegate = delegate; + } + + @Override + public R execute(CachingContext context) { + UnitOfWork work = context.getWork(); + Optional beforeExecutionState = context.getBeforeExecutionState(); + ExecutionStateChanges changes = context.getRebuildReason() + .map(rebuildReason -> + new RebuildExecutionStateChanges(rebuildReason, beforeExecutionState + .map(beforeExecution -> beforeExecution.getInputFileProperties()) + .orElse(null), + createIncrementalInputProperties(work)) + ) + .orElseGet(() -> + beforeExecutionState + .map(beforeExecution -> context.getAfterPreviousExecutionState() + .map(afterPreviousExecution -> changeDetector.detectChanges( + afterPreviousExecution, + beforeExecution, + work, + !work.isAllowOverlappingOutputs(), + createIncrementalInputProperties(work)) + ) + .orElseGet(() -> new RebuildExecutionStateChanges(NO_HISTORY, beforeExecution.getInputFileProperties(), createIncrementalInputProperties(work))) + ) + .orElse(null) + ); + + return delegate.execute(new IncrementalChangesContext() { + @Override + public Optional getChanges() { + return Optional.ofNullable(changes); + } + + @Override + public CachingState getCachingState() { + return context.getCachingState(); + } + + @Override + public Optional getRebuildReason() { + return context.getRebuildReason(); + } + + @Override + public Optional getAfterPreviousExecutionState() { + return context.getAfterPreviousExecutionState(); + } + + @Override + public Optional getBeforeExecutionState() { + return beforeExecutionState; + } + + @Override + public UnitOfWork getWork() { + return work; + } + }); + } + + private static IncrementalInputProperties createIncrementalInputProperties(UnitOfWork work) { + if (!work.isRequiresInputChanges()) { + return IncrementalInputProperties.NONE; + } + if (work.isRequiresLegacyInputChanges()) { + // When using IncrementalTaskInputs, keep the old behaviour of all file inputs being incremental + return IncrementalInputProperties.ALL; + } + ImmutableBiMap.Builder builder = ImmutableBiMap.builder(); + work.visitInputFileProperties((name, value, incremental) -> { + if (incremental) { + if (value == null) { + throw new InvalidUserDataException("Must specify a value for incremental input property '" + name + "'."); + } + builder.put(name, value); + } + }); + return new DefaultIncrementalInputProperties(builder.build()); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SkipUpToDateStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/SkipUpToDateStep.java similarity index 68% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SkipUpToDateStep.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/SkipUpToDateStep.java index 25db744fd6e99..1fde0ab7084e2 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SkipUpToDateStep.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/SkipUpToDateStep.java @@ -14,18 +14,20 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; -import com.google.common.collect.ImmutableCollection; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedMap; import org.apache.commons.lang.StringUtils; import org.gradle.caching.internal.origin.OriginMetadata; import org.gradle.internal.Try; -import org.gradle.internal.change.Change; -import org.gradle.internal.change.ChangeVisitor; import org.gradle.internal.execution.ExecutionOutcome; +import org.gradle.internal.execution.IncrementalChangesContext; +import org.gradle.internal.execution.SnapshotResult; +import org.gradle.internal.execution.Step; import org.gradle.internal.execution.UnitOfWork; +import org.gradle.internal.execution.UpToDateResult; +import org.gradle.internal.execution.history.AfterPreviousExecutionState; import org.gradle.internal.fingerprint.FileCollectionFingerprint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,10 +35,10 @@ import java.util.Formatter; import java.util.List; -public class SkipUpToDateStep implements Step { +public class SkipUpToDateStep implements Step { private static final Logger LOGGER = LoggerFactory.getLogger(SkipUpToDateStep.class); - private static final ImmutableList NO_HISTORY = ImmutableList.of("No history is available."); + private static final ImmutableList CHANGE_TRACKING_DISABLED = ImmutableList.of("Change tracking is disabled."); private final Step delegate; @@ -49,29 +51,28 @@ public UpToDateResult execute(C context) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Determining if {} is up-to-date", context.getWork().getDisplayName()); } - return context.getWork().getChangesSincePreviousExecution().map(changes -> { - ImmutableList.Builder builder = ImmutableList.builder(); - MessageCollectingChangeVisitor visitor = new MessageCollectingChangeVisitor(builder, 3); - changes.visitAllChanges(visitor); - ImmutableList reasons = builder.build(); + return context.getChanges().map(changes -> { + ImmutableList reasons = changes.getAllChangeMessages(); if (reasons.isEmpty()) { if (LOGGER.isInfoEnabled()) { LOGGER.info("Skipping {} as it is up-to-date.", context.getWork().getDisplayName()); } + @SuppressWarnings("OptionalGetWithoutIsPresent") + AfterPreviousExecutionState afterPreviousExecutionState = context.getAfterPreviousExecutionState().get(); return new UpToDateResult() { @Override - public ImmutableList getOutOfDateReasons() { + public ImmutableList getExecutionReasons() { return ImmutableList.of(); } @Override public ImmutableSortedMap getFinalOutputs() { - return changes.getPreviousExecution().getOutputFileProperties(); + return afterPreviousExecutionState.getOutputFileProperties(); } @Override public OriginMetadata getOriginMetadata() { - return changes.getPreviousExecution().getOriginMetadata(); + return afterPreviousExecutionState.getOriginMetadata(); } @Override @@ -87,15 +88,15 @@ public boolean isReused() { } else { return executeBecause(reasons, context); } - }).orElseGet(() -> executeBecause(NO_HISTORY, context)); + }).orElseGet(() -> executeBecause(CHANGE_TRACKING_DISABLED, context)); } private UpToDateResult executeBecause(ImmutableList reasons, C context) { - logOutOfDateReasons(reasons, context.getWork()); + logExecutionReasons(reasons, context.getWork()); SnapshotResult result = delegate.execute(context); return new UpToDateResult() { @Override - public ImmutableList getOutOfDateReasons() { + public ImmutableList getExecutionReasons() { return reasons; } @@ -121,7 +122,7 @@ public boolean isReused() { }; } - private void logOutOfDateReasons(List reasons, UnitOfWork work) { + private void logExecutionReasons(List reasons, UnitOfWork work) { if (LOGGER.isInfoEnabled()) { Formatter formatter = new Formatter(); formatter.format("%s is not up-to-date because:", StringUtils.capitalize(work.getDisplayName())); @@ -131,21 +132,4 @@ private void logOutOfDateReasons(List reasons, UnitOfWork work) { LOGGER.info(formatter.toString()); } } - - private static class MessageCollectingChangeVisitor implements ChangeVisitor { - private final ImmutableCollection.Builder messages; - private final int max; - private int count; - - public MessageCollectingChangeVisitor(ImmutableCollection.Builder messages, int max) { - this.messages = messages; - this.max = max; - } - - @Override - public boolean visitChange(Change change) { - messages.add(change.getMessage()); - return ++count < max; - } - } } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SnapshotOutputStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/SnapshotOutputsStep.java similarity index 87% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SnapshotOutputStep.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/SnapshotOutputsStep.java index 8e8214f45305c..15ce0f293336d 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/SnapshotOutputStep.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/SnapshotOutputsStep.java @@ -14,22 +14,25 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; import com.google.common.collect.ImmutableSortedMap; import org.gradle.caching.internal.origin.OriginMetadata; import org.gradle.internal.Try; +import org.gradle.internal.execution.Context; +import org.gradle.internal.execution.CurrentSnapshotResult; import org.gradle.internal.execution.ExecutionOutcome; import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; import org.gradle.internal.execution.UnitOfWork; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; import org.gradle.internal.id.UniqueId; -public class SnapshotOutputStep implements Step { +public class SnapshotOutputsStep implements Step { private final UniqueId buildInvocationScopeId; private final Step delegate; - public SnapshotOutputStep( + public SnapshotOutputsStep( UniqueId buildInvocationScopeId, Step delegate ) { diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/StoreSnapshotsStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/StoreSnapshotsStep.java new file mode 100644 index 0000000000000..171d345a1aa13 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/StoreSnapshotsStep.java @@ -0,0 +1,86 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps; + +import com.google.common.collect.ImmutableSortedMap; +import org.gradle.internal.change.ChangeDetectorVisitor; +import org.gradle.internal.execution.CurrentSnapshotResult; +import org.gradle.internal.execution.IncrementalContext; +import org.gradle.internal.execution.Step; +import org.gradle.internal.execution.UnitOfWork; +import org.gradle.internal.execution.history.AfterPreviousExecutionState; +import org.gradle.internal.execution.history.changes.OutputFileChanges; +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; +import org.gradle.internal.fingerprint.FileCollectionFingerprint; + +import java.util.Optional; + +public class StoreSnapshotsStep implements Step { + private final Step delegate; + + public StoreSnapshotsStep( + Step delegate + ) { + this.delegate = delegate; + } + + @Override + public CurrentSnapshotResult execute(C context) { + CurrentSnapshotResult result = delegate.execute(context); + ImmutableSortedMap finalOutputs = result.getFinalOutputs(); + context.getBeforeExecutionState().ifPresent(beforeExecutionState -> { + boolean successful = result.getOutcome().isSuccessful(); + // We do not store the history if there was a failure and the outputs did not change, since then the next execution can be incremental. + // For example the current execution fails because of a compile failure and for the next execution the source file is fixed, so only the one changed source file needs to be compiled. + if (successful + || didChangeOutput(context.getAfterPreviousExecutionState(), finalOutputs)) { + UnitOfWork work = context.getWork(); + work.getExecutionHistoryStore().store( + work.getIdentity(), + result.getOriginMetadata(), + beforeExecutionState.getImplementation(), + beforeExecutionState.getAdditionalImplementations(), + beforeExecutionState.getInputProperties(), + beforeExecutionState.getInputFileProperties(), + finalOutputs, + successful + ); + } + }); + return result; + } + + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") + private static boolean didChangeOutput(Optional afterPreviousExecutionState, ImmutableSortedMap current) { + // If there is no previous state, then we do have output changes + if (!afterPreviousExecutionState.isPresent()) { + return true; + } + + // If there are different output properties compared to the previous execution, then we do have output changes + ImmutableSortedMap previous = afterPreviousExecutionState.get().getOutputFileProperties(); + if (!previous.keySet().equals(current.keySet())) { + return true; + } + + // Otherwise do deep compare of outputs + ChangeDetectorVisitor visitor = new ChangeDetectorVisitor(); + OutputFileChanges changes = new OutputFileChanges(previous, current, true); + changes.accept(visitor); + return visitor.hasAnyChanges(); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/TimeoutStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/TimeoutStep.java similarity index 89% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/TimeoutStep.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/TimeoutStep.java index dbb89e8541bca..b61e32c454d20 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/TimeoutStep.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/TimeoutStep.java @@ -14,11 +14,13 @@ * limitations under the License. */ -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; import org.gradle.api.GradleException; import org.gradle.api.InvalidUserDataException; +import org.gradle.internal.execution.Context; import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; import org.gradle.internal.execution.UnitOfWork; import org.gradle.internal.execution.timeout.Timeout; import org.gradle.internal.execution.timeout.TimeoutHandler; @@ -30,7 +32,10 @@ public class TimeoutStep implements Step { private final TimeoutHandler timeoutHandler; private final Step delegate; - public TimeoutStep(TimeoutHandler timeoutHandler, Step delegate) { + public TimeoutStep( + TimeoutHandler timeoutHandler, + Step delegate + ) { this.timeoutHandler = timeoutHandler; this.delegate = delegate; } @@ -55,8 +60,7 @@ private Result executeWithTimeout(C context, Duration timeout) { try { return executeWithoutTimeout(context); } finally { - taskTimeout.stop(); - if (taskTimeout.timedOut()) { + if (taskTimeout.stop()) { //noinspection ResultOfMethodCallIgnored Thread.interrupted(); //noinspection ThrowFromFinallyBlock diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/legacy/MarkSnapshottingInputsFinishedStep.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/legacy/MarkSnapshottingInputsFinishedStep.java new file mode 100644 index 0000000000000..1b4ffe2753a68 --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/legacy/MarkSnapshottingInputsFinishedStep.java @@ -0,0 +1,38 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps.legacy; + +import org.gradle.internal.execution.CachingContext; +import org.gradle.internal.execution.Result; +import org.gradle.internal.execution.Step; + +/** + * This is a temporary measure for Gradle tasks to track a legacy measurement of all input snapshotting together. + */ +public class MarkSnapshottingInputsFinishedStep implements Step { + private final Step delegate; + + public MarkSnapshottingInputsFinishedStep(Step delegate) { + this.delegate = delegate; + } + + @Override + public R execute(CachingContext context) { + context.getWork().markSnapshottingInputsFinished(context.getCachingState()); + return delegate.execute(context); + } +} diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/legacy/package-info.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/legacy/package-info.java new file mode 100644 index 0000000000000..d4f3748d72a8a --- /dev/null +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/legacy/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NonNullApi +package org.gradle.internal.execution.steps.legacy; + +import org.gradle.api.NonNullApi; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/package-info.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/package-info.java similarity index 92% rename from subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/package-info.java rename to subprojects/execution/src/main/java/org/gradle/internal/execution/steps/package-info.java index 7b966c361628d..7564fbd4646de 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/impl/steps/package-info.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/steps/package-info.java @@ -14,6 +14,6 @@ * limitations under the License. */ @NonNullApi -package org.gradle.internal.execution.impl.steps; +package org.gradle.internal.execution.steps; import org.gradle.api.NonNullApi; diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/timeout/Timeout.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/timeout/Timeout.java index fad0d0d3df5c1..fb92feff2210a 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/timeout/Timeout.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/timeout/Timeout.java @@ -21,12 +21,7 @@ */ public interface Timeout { /** - * Returns whether the work did time out. + * Stops the timeout and returns whether the work did time out. */ - boolean timedOut(); - - /** - * Stops the timeout. - */ - void stop(); + boolean stop(); } diff --git a/subprojects/execution/src/main/java/org/gradle/internal/execution/timeout/impl/DefaultTimeoutHandler.java b/subprojects/execution/src/main/java/org/gradle/internal/execution/timeout/impl/DefaultTimeoutHandler.java index be58bf8cbf179..9a096e78b2f46 100644 --- a/subprojects/execution/src/main/java/org/gradle/internal/execution/timeout/impl/DefaultTimeoutHandler.java +++ b/subprojects/execution/src/main/java/org/gradle/internal/execution/timeout/impl/DefaultTimeoutHandler.java @@ -54,12 +54,8 @@ private DefaultTimeout(ScheduledFuture timeoutTask, InterruptOnTimeout interr } @Override - public void stop() { + public boolean stop() { timeoutTask.cancel(true); - } - - @Override - public boolean timedOut() { return interrupter.interrupted; } } diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/TestOutputFilesRepository.java b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/TestOutputFilesRepository.java deleted file mode 100644 index 3db553c61bb5a..0000000000000 --- a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/TestOutputFilesRepository.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.execution; - -import com.google.common.collect.Iterables; -import org.gradle.internal.execution.history.OutputFilesRepository; -import org.gradle.internal.snapshot.FileSystemSnapshot; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -public class TestOutputFilesRepository implements OutputFilesRepository { - - private List outputFileFingerprints = new ArrayList<>(); - - @Override - public boolean isGeneratedByGradle(File file) { - return true; - } - - @Override - public void recordOutputs(Iterable outputFileFingerprints) { - Iterables.addAll(this.outputFileFingerprints, outputFileFingerprints); - } - - public List getOutputFileFingerprints() { - return outputFileFingerprints; - } -} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/caching/impl/DefaultCachingStateBuilderTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/caching/impl/DefaultCachingStateBuilderTest.groovy new file mode 100644 index 0000000000000..3c6ddbd7e1c13 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/caching/impl/DefaultCachingStateBuilderTest.groovy @@ -0,0 +1,107 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.caching.impl + +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory +import org.gradle.internal.fingerprint.impl.EmptyCurrentFileCollectionFingerprint +import org.gradle.internal.hash.HashCode +import org.gradle.internal.snapshot.impl.ImplementationSnapshot +import org.gradle.internal.snapshot.impl.IntegerValueSnapshot +import org.gradle.internal.snapshot.impl.StringValueSnapshot +import spock.lang.Specification + +class DefaultCachingStateBuilderTest extends Specification { + def builder = new DefaultCachingStateBuilder() + + def implementation = ImplementationSnapshot.of("org.gradle.WorkType", HashCode.fromInt(1234)) + def additionalImplementation = ImplementationSnapshot.of("org.gradle.AdditionalWorkType", HashCode.fromInt(1234)) + + def "caching is enabled with valid inputs"() { + withValidInputs() + + when: + def cachingState = builder.build() + + then: + cachingState.key.present + cachingState.disabledReasons.empty + cachingState.inputs.get().implementation == implementation + cachingState.inputs.get().additionalImplementations == [additionalImplementation] + cachingState.inputs.get().inputValueFingerprints.keySet().toList() == ["input.number", "input.string"] + cachingState.inputs.get().inputFileFingerprints.keySet().toList() == ["input.files"] + cachingState.inputs.get().outputProperties.toList() == ["output"] + } + + def "caching is disabled when cache key is invalid because of invalid implementation"() { + builder.withImplementation(ImplementationSnapshot.of("org.gradle.WorkType", null)) + + when: + def cachingState = builder.build() + + then: + !cachingState.key.present + cachingState.disabledReasons*.category == [CachingDisabledReasonCategory.NON_CACHEABLE_IMPLEMENTATION] + cachingState.disabledReasons*.message == ["Implementation type was loaded with an unknown classloader (class 'org.gradle.WorkType')."] + } + + def "caching is disabled when cache key is invalid because of invalid additional implementation"() { + withValidInputs() + builder.withAdditionalImplementations([ImplementationSnapshot.of("org.gradle.AdditionalWorkType", null)]) + + when: + def cachingState = builder.build() + + then: + !cachingState.key.present + cachingState.disabledReasons*.category == [CachingDisabledReasonCategory.NON_CACHEABLE_ADDITIONAL_IMPLEMENTATION] + cachingState.disabledReasons*.message == ["Additional implementation type was loaded with an unknown classloader (class 'org.gradle.AdditionalWorkType')."] + } + + def "caching is disabled when cache key is invalid because of invalid input"() { + withValidInputs() + builder.withInputValueFingerprints( + "input.invalid": ImplementationSnapshot.of("org.gradle.WorkType", null) + ) + + when: + def cachingState = builder.build() + + then: + !cachingState.key.present + cachingState.disabledReasons*.category == [CachingDisabledReasonCategory.NON_CACHEABLE_INPUTS] + cachingState.disabledReasons*.message == ["Non-cacheable inputs: property 'input.invalid' was loaded with an unknown classloader (class 'org.gradle.WorkType')."] + } + + def withValidInputs() { + builder.withImplementation( + implementation + ) + builder.withAdditionalImplementations([ + additionalImplementation + ]) + builder.withInputValueFingerprints( + "input.string": new StringValueSnapshot("input"), + "input.number": new IntegerValueSnapshot(123) + ) + builder.withInputFilePropertyFingerprints( + "input.files": new EmptyCurrentFileCollectionFingerprint("test") + ) + builder.withOutputPropertyNames([ + "output" + ]) + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/history/changes/ImplementationChangesTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/history/changes/ImplementationChangesTest.groovy index fa178a9c228e9..dafff9f384c4b 100644 --- a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/history/changes/ImplementationChangesTest.groovy +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/history/changes/ImplementationChangesTest.groovy @@ -21,8 +21,7 @@ import org.gradle.api.DefaultTask import org.gradle.api.Describable import org.gradle.api.Task import org.gradle.api.internal.TaskInternal -import org.gradle.api.internal.tasks.ContextAwareTaskAction -import org.gradle.api.internal.tasks.TaskExecutionContext +import org.gradle.api.internal.tasks.InputChangesAwareTaskAction import org.gradle.internal.Cast import org.gradle.internal.change.CollectingChangeVisitor import org.gradle.internal.classloader.ClassLoaderHierarchyHasher @@ -153,13 +152,13 @@ class ImplementationChangesTest extends Specification { private class SimpleTask extends DefaultTask {} private class PreviousTask extends DefaultTask {} - private static class TestAction implements ContextAwareTaskAction { + private static class TestAction implements InputChangesAwareTaskAction { @Override - void contextualise(TaskExecutionContext context) { + void setInputChanges(InputChangesInternal inputChanges) { } @Override - void releaseContext() { + void clearInputChanges() { } @Override diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/history/changes/NonIncrementalInputChangesTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/history/changes/NonIncrementalInputChangesTest.groovy new file mode 100644 index 0000000000000..f77a4b6213937 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/history/changes/NonIncrementalInputChangesTest.groovy @@ -0,0 +1,51 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.history.changes + +import com.google.common.collect.ImmutableBiMap +import com.google.common.collect.ImmutableSortedMap +import org.gradle.api.file.FileSystemLocation +import org.gradle.api.provider.Provider +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint +import org.gradle.internal.fingerprint.impl.AbsolutePathFingerprintingStrategy +import org.gradle.internal.fingerprint.impl.DefaultCurrentFileCollectionFingerprint +import org.gradle.internal.hash.HashCode +import org.gradle.internal.snapshot.RegularFileSnapshot +import spock.lang.Specification + +class NonIncrementalInputChangesTest extends Specification { + + def "can iterate changes more than once"() { + def fingerprint = DefaultCurrentFileCollectionFingerprint.from([new RegularFileSnapshot("/some/where", "where", HashCode.fromInt(1234), 0)], AbsolutePathFingerprintingStrategy.INCLUDE_MISSING) + + Provider value = Mock() + def changes = new NonIncrementalInputChanges(ImmutableSortedMap.of("input", fingerprint), new DefaultIncrementalInputProperties(ImmutableBiMap.of("input", value))) + def expectedChangedFiles = [new File("/some/where")] + + when: + def allFileChanges = changes.allFileChanges + def fileChanges = changes.getFileChanges(value) + + then: + allFileChanges*.file == expectedChangedFiles + allFileChanges*.file == expectedChangedFiles + + fileChanges*.file == expectedChangedFiles + fileChanges*.file == expectedChangedFiles + } + +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/CacheStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/CacheStepTest.groovy deleted file mode 100644 index 937b20d06b7bb..0000000000000 --- a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/CacheStepTest.groovy +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.execution.impl.steps - -import com.google.common.collect.ImmutableSortedMap -import org.gradle.caching.internal.command.BuildCacheCommandFactory -import org.gradle.caching.internal.controller.BuildCacheController -import org.gradle.caching.internal.origin.OriginMetadata -import org.gradle.internal.Try -import org.gradle.internal.execution.CacheHandler -import org.gradle.internal.execution.ExecutionOutcome -import org.gradle.internal.execution.OutputChangeListener -import org.gradle.internal.execution.UnitOfWork -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint -import org.gradle.internal.fingerprint.impl.EmptyCurrentFileCollectionFingerprint -import org.gradle.internal.id.UniqueId -import spock.lang.Specification - -class CacheStepTest extends Specification { - - def buildCacheController = Mock(BuildCacheController) - def buildCacheCommandFactory = Mock(BuildCacheCommandFactory) - def outputChangeListener = Mock(OutputChangeListener) - def currentBuildId = UniqueId.generate() - Step delegateStep = Mock(Step) - def cacheStep = new CacheStep(buildCacheController, outputChangeListener, buildCacheCommandFactory, delegateStep) - def cacheHandler = Mock(CacheHandler) - def unitOfWork = Mock(UnitOfWork) - def loadMetadata = Mock(BuildCacheCommandFactory.LoadMetadata) - def cachedOriginMetadata = Mock(OriginMetadata) - def context = new CachingContext() { - CacheHandler getCacheHandler() { - CacheStepTest.this.cacheHandler - } - UnitOfWork getWork() { - unitOfWork - } - } - - def "loads from cache"() { - def outputsFromCache = ImmutableSortedMap.of("test", new EmptyCurrentFileCollectionFingerprint()) - - when: - def result = cacheStep.execute(context) - def originMetadata = result.originMetadata - def finalOutputs = result.finalOutputs - then: - result.outcome.get() == ExecutionOutcome.FROM_CACHE - result.reused - originMetadata == cachedOriginMetadata - finalOutputs == outputsFromCache - - 1 * buildCacheController.isEnabled() >> true - 1 * cacheHandler.load(_) >> Optional.of(loadMetadata) - 1 * loadMetadata.originMetadata >> cachedOriginMetadata - 1 * loadMetadata.resultingSnapshots >> outputsFromCache - 0 * _ - } - - def "executes work and stores in cache on cache miss"() { - def executionResult = new CurrentSnapshotResult() { - final ImmutableSortedMap finalOutputs = ImmutableSortedMap.of("test", new EmptyCurrentFileCollectionFingerprint()) - final OriginMetadata originMetadata = new OriginMetadata(currentBuildId, 0) - final Try outcome = Try.successful(ExecutionOutcome.EXECUTED) - final boolean reused = false - } - - when: - def result = cacheStep.execute(context) - - then: - result == executionResult - !result.reused - - 1 * buildCacheController.isEnabled() >> true - 1 * cacheHandler.load(_) >> Optional.empty() - 1 * delegateStep.execute(_) >> executionResult - 1 * cacheHandler.store(_) - 0 * _ - } - - def "failures are not stored in the cache"() { - def failedResult = new CurrentSnapshotResult() { - final ImmutableSortedMap finalOutputs = ImmutableSortedMap.of("test", new EmptyCurrentFileCollectionFingerprint()) - final OriginMetadata originMetadata = new OriginMetadata(currentBuildId, 0) - final Try outcome = Try.failure(new RuntimeException("failed")) - final boolean reused = false - } - - when: - def result = cacheStep.execute(context) - - then: - result == failedResult - !result.reused - - 1 * buildCacheController.isEnabled() >> true - 1 * cacheHandler.load(_) >> Optional.empty() - 1 * delegateStep.execute(_) >> failedResult - _ * unitOfWork.displayName >> "Display name" - 0 * cacheHandler.store(_) - 0 * _ - } - - def "executes when caching is disabled"() { - def executionResult = new CurrentSnapshotResult() { - final ImmutableSortedMap finalOutputs = ImmutableSortedMap.of("test", new EmptyCurrentFileCollectionFingerprint()) - final OriginMetadata originMetadata = new OriginMetadata(currentBuildId, 0) - final Try outcome = Try.successful(ExecutionOutcome.EXECUTED) - final boolean reused = false - } - when: - def result = cacheStep.execute(context) - then: - result == executionResult - - 1 * buildCacheController.isEnabled() >> false - 1 * delegateStep.execute(_) >> executionResult - 0 * _ - } -} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/ExecutionTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/ExecutionTest.groovy deleted file mode 100644 index 622e2cf305023..0000000000000 --- a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/ExecutionTest.groovy +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.execution.impl.steps - -import com.google.common.collect.ImmutableSortedMap -import org.gradle.api.BuildCancelledException -import org.gradle.caching.internal.origin.OriginMetadata -import org.gradle.initialization.DefaultBuildCancellationToken -import org.gradle.internal.execution.CacheHandler -import org.gradle.internal.execution.ExecutionException -import org.gradle.internal.execution.ExecutionOutcome -import org.gradle.internal.execution.OutputChangeListener -import org.gradle.internal.execution.UnitOfWork -import org.gradle.internal.execution.history.changes.ExecutionStateChanges -import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint -import spock.lang.Specification - -import java.time.Duration -import java.util.function.BooleanSupplier - -class ExecutionTest extends Specification { - - def outputChangeListener = Mock(OutputChangeListener) - def cancellationToken = new DefaultBuildCancellationToken() - def executionStep = new CatchExceptionStep( - new CancelExecutionStep(cancellationToken, - new ExecuteStep(outputChangeListener) - ) - ) - - def "executes the unit of work"() { - def unitOfWork = new TestUnitOfWork({ -> - return true - }) - when: - def result = executionStep.execute { -> unitOfWork} - - then: - unitOfWork.executed - result.outcome.get() == ExecutionOutcome.EXECUTED - - 1 * outputChangeListener.beforeOutputChange() - 0 * _ - } - - def "reports no work done"() { - when: - def result = executionStep.execute { -> - new TestUnitOfWork({ -> - return false - }) - } - - then: - result.outcome.get() == ExecutionOutcome.UP_TO_DATE - - 1 * outputChangeListener.beforeOutputChange() - 0 * _ - } - - def "catches failures"() { - def failure = new RuntimeException("broken") - def unitOfWork = new TestUnitOfWork({ -> - throw failure - }) - - when: - def result = executionStep.execute { -> unitOfWork } - - then: - result.outcome.failure.get() instanceof ExecutionException - result.outcome.failure.get().cause == failure - result.outcome.failure.get().message.contains(unitOfWork.displayName) - - 1 * outputChangeListener.beforeOutputChange() - 0 * _ - } - - def "invalidates only changing outputs"() { - def changingOutputs = ['some/location'] - def unitOfWork = new TestUnitOfWork({ -> true }, changingOutputs) - - when: - def result = executionStep.execute { -> unitOfWork } - - then: - result.outcome.get() == ExecutionOutcome.EXECUTED - - 1 * outputChangeListener.beforeOutputChange(changingOutputs) - 0 * _ - } - - def "fails the execution when build has been cancelled"() { - def unitOfWork = new TestUnitOfWork({ -> true }) - - when: - cancellationToken.cancel() - def result = executionStep.execute { -> unitOfWork } - - then: - result.outcome.failure.get() instanceof ExecutionException - result.outcome.failure.get().cause instanceof BuildCancelledException - - 1 * outputChangeListener.beforeOutputChange() - 0 * _ - } - - static class TestUnitOfWork implements UnitOfWork { - - private final BooleanSupplier work - private final Iterable changingOutputs - - TestUnitOfWork(BooleanSupplier work = { -> true}, Iterable changingOutputs = null) { - this.changingOutputs = changingOutputs - this.work = work - } - - boolean executed - - @Override - ExecutionOutcome execute() { - executed = true - return work.asBoolean ? ExecutionOutcome.EXECUTED : ExecutionOutcome.UP_TO_DATE - } - - @Override - Optional getTimeout() { - throw new UnsupportedOperationException() - } - - @Override - void visitOutputProperties(OutputPropertyVisitor visitor) { - throw new UnsupportedOperationException() - } - - @Override - long markExecutionTime() { - throw new UnsupportedOperationException() - } - - @Override - void visitLocalState(LocalStateVisitor visitor) { - throw new UnsupportedOperationException() - } - - @Override - void outputsRemovedAfterFailureToLoadFromCache() { - throw new UnsupportedOperationException() - } - - @Override - CacheHandler createCacheHandler() { - throw new UnsupportedOperationException() - } - - @Override - void persistResult(ImmutableSortedMap finalOutputs, boolean successful, OriginMetadata originMetadata) { - throw new UnsupportedOperationException() - } - - @Override - Optional getChangesSincePreviousExecution() { - throw new UnsupportedOperationException() - } - - @Override - Optional> getChangingOutputs() { - Optional.ofNullable(changingOutputs) - } - - @Override - ImmutableSortedMap snapshotAfterOutputsGenerated() { - throw new UnsupportedOperationException() - } - - @Override - String getIdentity() { - throw new UnsupportedOperationException() - } - - @Override - void visitOutputTrees(CacheableTreeVisitor visitor) { - throw new UnsupportedOperationException() - } - - @Override - String getDisplayName() { - "Test unit of work" - } - } - -} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/SkipUpToDateStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/SkipUpToDateStepTest.groovy deleted file mode 100644 index 5895a344eb8c8..0000000000000 --- a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/impl/steps/SkipUpToDateStepTest.groovy +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.execution.impl.steps - -import org.gradle.internal.change.ChangeVisitor -import org.gradle.internal.change.DescriptiveChange -import org.gradle.internal.execution.ExecutionOutcome -import org.gradle.internal.execution.UnitOfWork -import org.gradle.internal.execution.history.changes.ExecutionStateChanges -import org.gradle.testing.internal.util.Specification - -class SkipUpToDateStepTest extends Specification { - def delegate = Mock(Step) - def step = new SkipUpToDateStep(delegate) - def context = Mock(Context) - def work = Mock(UnitOfWork) - def changes = Mock(ExecutionStateChanges) - - def "skips when outputs are up to date"() { - when: - def result = step.execute(context) - - then: - result.outcome.get() == ExecutionOutcome.UP_TO_DATE - result.outOfDateReasons.empty - - _ * context.work >> work - 1 * work.changesSincePreviousExecution >> Optional.of(changes) - 1 * changes.visitAllChanges(_) >> {} - 0 * _ - } - - def "executes when outputs are not up to date"() { - when: - def result = step.execute(context) - - then: - result.outOfDateReasons == ["change"] - - _ * context.work >> work - 1 * work.changesSincePreviousExecution >> Optional.of(changes) - 1 * changes.visitAllChanges(_) >> { ChangeVisitor visitor -> - visitor.visitChange(new DescriptiveChange("change")) - } - 1 * delegate.execute(context) - 0 * _ - } - - def "executes when there's no history available"() { - when: - def result = step.execute(context) - - then: - result.outOfDateReasons == ["No history is available."] - - _ * context.work >> work - 1 * work.changesSincePreviousExecution >> Optional.empty() - 1 * delegate.execute(context) - 0 * _ - } -} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/BroadcastChangingOutputsStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/BroadcastChangingOutputsStepTest.groovy new file mode 100644 index 0000000000000..7b031af4bdd3b --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/BroadcastChangingOutputsStepTest.groovy @@ -0,0 +1,66 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import org.gradle.internal.execution.Context +import org.gradle.internal.execution.OutputChangeListener +import org.gradle.internal.execution.Result + +class BroadcastChangingOutputsStepTest extends StepSpec { + def outputChangeListener = Mock(OutputChangeListener) + def step = new BroadcastChangingOutputsStep<>(outputChangeListener, delegate) + def context = Mock(Context) + def delegateResult = Mock(Result) + + def "notifies listener about all outputs changing"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * context.work >> work + 1 * work.changingOutputs >> Optional.empty() + + then: + 1 * outputChangeListener.beforeOutputChange() + + then: + 1 * delegate.execute(context) >> delegateResult + 0 * _ + } + + def "notifies listener about specific outputs changing"() { + def changingOutputs = ["output.txt"] + + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * context.work >> work + 1 * work.changingOutputs >> Optional.of(changingOutputs) + + then: + 1 * outputChangeListener.beforeOutputChange(changingOutputs) + + then: + 1 * delegate.execute(context) >> delegateResult + 0 * _ + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CacheStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CacheStepTest.groovy new file mode 100644 index 0000000000000..61509beabf7ee --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CacheStepTest.groovy @@ -0,0 +1,296 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import com.google.common.collect.ImmutableList +import org.gradle.api.internal.file.collections.ImmutableFileCollection +import org.gradle.caching.BuildCacheKey +import org.gradle.caching.internal.command.BuildCacheCommandFactory +import org.gradle.caching.internal.controller.BuildCacheController +import org.gradle.caching.internal.controller.BuildCacheLoadCommand +import org.gradle.caching.internal.controller.BuildCacheStoreCommand +import org.gradle.caching.internal.origin.OriginMetadata +import org.gradle.internal.Try +import org.gradle.internal.execution.CurrentSnapshotResult +import org.gradle.internal.execution.ExecutionOutcome +import org.gradle.internal.execution.IncrementalChangesContext +import org.gradle.internal.execution.UnitOfWork +import org.gradle.internal.execution.caching.CachingDisabledReason +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory +import org.gradle.internal.execution.caching.CachingState +import org.gradle.internal.execution.history.changes.ExecutionStateChanges +import org.gradle.internal.file.TreeType +import spock.lang.Shared +import spock.lang.Unroll + +class CacheStepTest extends StepSpec implements FingerprinterFixture { + def buildCacheController = Mock(BuildCacheController) + def buildCacheCommandFactory = Mock(BuildCacheCommandFactory) + + def cacheKey = Stub(BuildCacheKey) + def cachingState = Mock(CachingState) + def loadMetadata = Mock(BuildCacheCommandFactory.LoadMetadata) + @Shared def rebuildChanges = Mock(ExecutionStateChanges) + def localStateFile = file("local-state.txt") << "local state" + + def step = new CacheStep(buildCacheController, buildCacheCommandFactory, delegate) + def delegateResult = Mock(CurrentSnapshotResult) + def context = Mock(IncrementalChangesContext) + + def loadCommand = Mock(BuildCacheLoadCommand) + + def "loads from cache"() { + def cachedOriginMetadata = Mock(OriginMetadata) + def outputsFromCache = fingerprintsOf("test": []) + + when: + def result = step.execute(context) + + then: + result.outcome.get() == ExecutionOutcome.FROM_CACHE + result.reused + result.originMetadata == cachedOriginMetadata + result.finalOutputs == outputsFromCache + + interaction { withValidCacheKey() } + + then: + 1 * work.allowedToLoadFromCache >> true + 1 * buildCacheCommandFactory.createLoad(cacheKey, work) >> loadCommand + 1 * buildCacheController.load(loadCommand) >> Optional.of(loadMetadata) + + then: + 1 * loadMetadata.originMetadata >> cachedOriginMetadata + 1 * loadMetadata.resultingSnapshots >> outputsFromCache + interaction { localStateIsRemoved() } + 0 * _ + } + + def "executes work and stores in cache on cache miss"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + interaction { withValidCacheKey() } + + then: + 1 * work.allowedToLoadFromCache >> true + 1 * buildCacheCommandFactory.createLoad(cacheKey, work) >> loadCommand + 1 * buildCacheController.load(loadCommand) >> Optional.empty() + + then: + 1 * delegate.execute(context) >> delegateResult + 1 * delegateResult.outcome >> Try.successful(ExecutionOutcome.EXECUTED_NON_INCREMENTALLY) + + then: + 1 * context.work >> work + interaction { outputStored {} } + 0 * _ + } + + @Unroll + def "executes work #description non-incrementally and stores after unpack failure"() { + def loadedOutputFile = file("output.txt") + def loadedOutputDir = file("output") + + when: + def result = step.execute(context) + + then: + result == delegateResult + + interaction { withValidCacheKey() } + + then: + 1 * work.allowedToLoadFromCache >> true + 1 * buildCacheCommandFactory.createLoad(cacheKey, work) >> loadCommand + 1 * buildCacheController.load(loadCommand) >> { BuildCacheLoadCommand command -> + loadedOutputFile << "output" + loadedOutputDir.mkdirs() + loadedOutputDir.file("output.txt") << "output" + throw new RuntimeException("unpack failure") + } + + then: + 1 * work.displayName >> "work" + 1 * work.visitOutputProperties(_) >> { UnitOfWork.OutputPropertyVisitor visitor -> + visitor.visitOutputProperty("outputFile", TreeType.FILE, ImmutableFileCollection.of(loadedOutputFile)) + visitor.visitOutputProperty("outputDir", TreeType.DIRECTORY, ImmutableFileCollection.of(loadedOutputDir)) + visitor.visitOutputProperty("missingOutputFile", TreeType.FILE, ImmutableFileCollection.of(file("missing.txt"))) + visitor.visitOutputProperty("missingOutputDir", TreeType.DIRECTORY, ImmutableFileCollection.of(file("missing"))) + } + loadedOutputFile.assertDoesNotExist() + loadedOutputDir.assertIsEmptyDir() + interaction { localStateIsRemoved() } + + then: + 1 * context.changes >> Optional.ofNullable(changes) + 1 * delegate.execute(_) >> { IncrementalChangesContext delegateContext -> + assert delegateContext != context + check(delegateContext) + delegateResult + } + 1 * delegateResult.outcome >> Try.successful(ExecutionOutcome.EXECUTED_NON_INCREMENTALLY) + + then: + interaction { outputStored {} } + 0 * _ + + where: + description | check | changes + "without changes" | { IncrementalChangesContext context -> assert !context.getChanges().present } | null + "with changes" | { IncrementalChangesContext context -> assert context.getChanges().get() == rebuildChanges } | Mock(ExecutionStateChanges) { + 1 * withEnforcedRebuild("Outputs removed due to failed load from cache") >> rebuildChanges + } + } + + def "propagates non-recoverable unpack failure"() { + when: + step.execute(context) + + then: + def ex = thrown Exception + ex.message == "cleanup failure" + + interaction { withValidCacheKey() } + + then: + 1 * work.allowedToLoadFromCache >> true + 1 * buildCacheCommandFactory.createLoad(cacheKey, work) >> loadCommand + 1 * buildCacheController.load(loadCommand) >> { throw new RuntimeException("unpack failure") } + + then: + 1 * work.displayName >> "work" + 1 * work.visitOutputProperties(_) >> { + throw new RuntimeException("cleanup failure") + } + interaction { localStateIsRemoved() } + 0 * _ + } + + def "does not store result of failed execution in cache"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + !result.reused + + interaction { withValidCacheKey() } + + then: + 1 * work.allowedToLoadFromCache >> true + 1 * buildCacheCommandFactory.createLoad(cacheKey, work) >> loadCommand + 1 * buildCacheController.load(loadCommand) >> Optional.empty() + + then: + 1 * delegate.execute(context) >> delegateResult + 1 * delegateResult.outcome >> Try.failure(new RuntimeException("failure")) + + then: + 1 * context.work >> work + 1 * work.displayName >> "Display name" + 0 * buildCacheController.store(_) + 0 * _ + } + + def "does not load but stores when loading is disabled"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + interaction { withValidCacheKey() } + + then: + 1 * work.allowedToLoadFromCache >> false + + then: + 1 * delegate.execute(context) >> delegateResult + 1 * delegateResult.outcome >> Try.successful(ExecutionOutcome.EXECUTED_NON_INCREMENTALLY) + + then: + 1 * context.work >> work + interaction { outputStored {} } + 0 * _ + } + + def "does not fail when cache backend throws exception while storing cached result"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + interaction { withValidCacheKey() } + + then: + 1 * work.allowedToLoadFromCache >> false + + then: + 1 * delegate.execute(context) >> delegateResult + 1 * delegateResult.outcome >> Try.successful(ExecutionOutcome.EXECUTED_NON_INCREMENTALLY) + + then: + 1 * context.work >> work + interaction { outputStored { throw new RuntimeException("store failure") } } + 0 * _ + } + + def "executes and doesn't store when caching is disabled"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + !result.reused + + 1 * context.cachingState >> cachingState + 1 * cachingState.disabledReasons >> ImmutableList.of(new CachingDisabledReason(CachingDisabledReasonCategory.UNKNOWN, "Unknown")) + 1 * delegate.execute(_) >> delegateResult + 0 * _ + } + + private void withValidCacheKey() { + 1 * context.work >> work + 1 * context.cachingState >> cachingState + 1 * cachingState.disabledReasons >> ImmutableList.of() + 1 * cachingState.key >> Optional.of(cacheKey) + } + + private void outputStored(Closure storeResult) { + def originMetadata = Mock(OriginMetadata) + def finalOutputs = fingerprintsOf("test": []) + def storeCommand = Mock(BuildCacheStoreCommand) + + 1 * delegateResult.finalOutputs >> finalOutputs + 1 * delegateResult.originMetadata >> originMetadata + 1 * originMetadata.executionTime >> 123L + 1 * buildCacheCommandFactory.createStore(cacheKey, work, finalOutputs, 123L) >> storeCommand + 1 * buildCacheController.store(storeCommand) >> { storeResult() } + } + + private void localStateIsRemoved() { + 1 * work.visitLocalState(_) >> { UnitOfWork.LocalStateVisitor visitor -> + visitor.visitLocalStateRoot(localStateFile) + } + !localStateFile.exists() + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CancelExecutionStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CancelExecutionStepTest.groovy new file mode 100644 index 0000000000000..ee9f190984736 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CancelExecutionStepTest.groovy @@ -0,0 +1,59 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import org.gradle.api.BuildCancelledException +import org.gradle.initialization.BuildCancellationToken +import org.gradle.internal.execution.Context +import org.gradle.internal.execution.Result + +class CancelExecutionStepTest extends StepSpec { + def cancellationToken = Mock(BuildCancellationToken) + def step = new CancelExecutionStep(cancellationToken, delegate) + def context = Mock(Context) + def delegateResult = Mock(Result) + + def "executes normally when cancellation is not requested"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * cancellationToken.cancellationRequested >> false + 0 *_ + } + + def "cancels execution when cancellation is requested"() { + when: + step.execute(context) + + then: + thrown BuildCancelledException + + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * cancellationToken.cancellationRequested >> true + 1 * context.work >> work + 1 * work.displayName >> "cancelled work" + 0 *_ + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CatchExceptionStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CatchExceptionStepTest.groovy new file mode 100644 index 0000000000000..50c8a826fffed --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CatchExceptionStepTest.groovy @@ -0,0 +1,55 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import org.gradle.internal.execution.Context +import org.gradle.internal.execution.IncrementalChangesContext +import org.gradle.internal.execution.Result +import spock.lang.Unroll + +class CatchExceptionStepTest extends StepSpec { + def step = new CatchExceptionStep(delegate) + def context = Mock(IncrementalChangesContext) + + def "successful result is preserved"() { + def delegateResult = Mock(Result) + + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * delegate.execute(context) >> delegateResult + 0 * _ + } + + @Unroll + def "failure #failure.class.simpleName is caught"() { + when: + def result = step.execute(context) + + then: + result.outcome.failure.get() == failure + + 1 * delegate.execute(context) >> { throw failure } + 0 * _ + + where: + failure << [new RuntimeException(), new Error()] + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CreateOutputsStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CreateOutputsStepTest.groovy new file mode 100644 index 0000000000000..156257f2a10b8 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/CreateOutputsStepTest.groovy @@ -0,0 +1,70 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import org.gradle.api.internal.file.collections.ImmutableFileCollection +import org.gradle.internal.execution.Context +import org.gradle.internal.execution.Result +import org.gradle.internal.execution.UnitOfWork +import org.gradle.internal.file.TreeType + +class CreateOutputsStepTest extends StepSpec { + def context = Stub(Context) { + getWork() >> work + } + def step = new CreateOutputsStep(delegate) + + def "outputs are created"() { + when: + step.execute(context) + + then: + 1 * work.visitOutputProperties(_ as UnitOfWork.OutputPropertyVisitor) >> { UnitOfWork.OutputPropertyVisitor visitor -> + visitor.visitOutputProperty("dir", TreeType.DIRECTORY, ImmutableFileCollection.of(file("outDir"))) + visitor.visitOutputProperty("dirs", TreeType.DIRECTORY, ImmutableFileCollection.of(file("outDir1"), file("outDir2"))) + visitor.visitOutputProperty("file", TreeType.FILE, ImmutableFileCollection.of(file("parent/outFile"))) + visitor.visitOutputProperty("files", TreeType.FILE, ImmutableFileCollection.of(file("parent1/outFile"), file("parent2/outputFile1"), file("parent2/outputFile2"))) + } + + then: + def allDirs = ["outDir", "outDir1", "outDir2"].collect { file(it) } + def allFiles = ["parent/outFile", "parent1/outFile1", "parent2/outFile1", "parent2/outFile2"].collect { file(it) } + allDirs.each { + assert it.isDirectory() + } + allFiles.each { + assert it.parentFile.isDirectory() + assert !it.exists() + } + + then: + 1 * delegate.execute(context) + 0 * _ + } + + def "result is preserved"() { + def expected = Mock(Result) + when: + def result = step.execute(context) + + then: + result == expected + 1 * work.visitOutputProperties(_ as UnitOfWork.OutputPropertyVisitor) + 1 * delegate.execute(context) >> expected + 0 * _ + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ExecuteStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ExecuteStepTest.groovy new file mode 100644 index 0000000000000..bd39b206ca5fc --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ExecuteStepTest.groovy @@ -0,0 +1,96 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import org.gradle.internal.execution.ExecutionOutcome +import org.gradle.internal.execution.IncrementalChangesContext +import org.gradle.internal.execution.UnitOfWork +import org.gradle.internal.execution.history.changes.ExecutionStateChanges +import org.gradle.internal.execution.history.changes.InputChangesInternal +import spock.lang.Specification +import spock.lang.Unroll + +class ExecuteStepTest extends Specification { + def step = new ExecuteStep() + def context = Mock(IncrementalChangesContext) + def work = Mock(UnitOfWork) + def changes = Mock(ExecutionStateChanges) + def optionalChanges = Optional.of(changes) + def inputChanges = Mock(InputChangesInternal) + + @Unroll + def "result #workResult yields outcome #outcome (incremental false)"() { + when: + def result = step.execute(context) + + then: + result.outcome.get() == outcome + + 1 * context.work >> work + 1 * work.requiresInputChanges >> false + 1 * work.execute(null) >> workResult + 0 * _ + + where: + workResult | outcome + UnitOfWork.WorkResult.DID_WORK | ExecutionOutcome.EXECUTED_NON_INCREMENTALLY + UnitOfWork.WorkResult.DID_NO_WORK | ExecutionOutcome.UP_TO_DATE + } + + @Unroll + def "failure #failure.class.simpleName is not caught"() { + when: + step.execute(context) + + then: + def ex = thrown Throwable + ex == failure + + 1 * context.work >> work + 1 * work.requiresInputChanges >> false + 1 * work.execute(null) >> { throw failure } + 0 * _ + + where: + failure << [new RuntimeException(), new Error()] + } + + @Unroll + def "incremental work with result #workResult yields outcome #outcome (executed incrementally: #incrementalExecution)"() { + when: + def result = step.execute(context) + + then: + result.outcome.get() == outcome + + 1 * context.work >> work + 1 * work.requiresInputChanges >> true + 1 * context.changes >> optionalChanges + 1 * changes.createInputChanges() >> inputChanges + 1 * work.execute(inputChanges) >> workResult + _ * work.getDisplayName() + 2 * inputChanges.incremental >> incrementalExecution + 0 * _ + + where: + incrementalExecution | workResult | outcome + true | UnitOfWork.WorkResult.DID_WORK | ExecutionOutcome.EXECUTED_INCREMENTALLY + false | UnitOfWork.WorkResult.DID_WORK | ExecutionOutcome.EXECUTED_NON_INCREMENTALLY + true | UnitOfWork.WorkResult.DID_NO_WORK | ExecutionOutcome.UP_TO_DATE + false | UnitOfWork.WorkResult.DID_NO_WORK | ExecutionOutcome.UP_TO_DATE + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/FingerprinterFixture.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/FingerprinterFixture.groovy new file mode 100644 index 0000000000000..82fedaa454e18 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/FingerprinterFixture.groovy @@ -0,0 +1,64 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import com.google.common.collect.ImmutableSortedMap +import org.gradle.api.internal.cache.StringInterner +import org.gradle.api.internal.file.TestFiles +import org.gradle.api.internal.file.collections.ImmutableFileCollection +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint +import org.gradle.internal.fingerprint.impl.AbsolutePathFileCollectionFingerprinter +import org.gradle.internal.hash.TestFileHasher +import org.gradle.internal.snapshot.WellKnownFileLocations +import org.gradle.internal.snapshot.impl.DefaultFileSystemMirror +import org.gradle.internal.snapshot.impl.DefaultFileSystemSnapshotter +import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider + +abstract trait FingerprinterFixture { + abstract TestNameTestDirectoryProvider getTemporaryFolder() + + final fingerprinter = new AbsolutePathFileCollectionFingerprinter( + new DefaultFileSystemSnapshotter( + new TestFileHasher(), + new StringInterner(), + TestFiles.fileSystem(), + new DefaultFileSystemMirror(new NoWellKnownFileLocations()) + ) + ) + + ImmutableSortedMap fingerprintsOf(Map properties) { + def builder = ImmutableSortedMap.naturalOrder() + properties.each { propertyName, value -> + def files = (value instanceof Iterable + ? (Collection) value + : [value]).collect { + it instanceof File + ? it + : temporaryFolder.file(it) + } + builder.put(propertyName, fingerprinter.fingerprint(ImmutableFileCollection.of(files))) + } + return builder.build() + } + + private static class NoWellKnownFileLocations implements WellKnownFileLocations { + @Override + boolean isImmutable(String path) { + return false + } + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/RecordOutputsStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/RecordOutputsStepTest.groovy new file mode 100644 index 0000000000000..55d04680a5df3 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/RecordOutputsStepTest.groovy @@ -0,0 +1,48 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import org.gradle.internal.execution.Context +import org.gradle.internal.execution.CurrentSnapshotResult +import org.gradle.internal.execution.history.OutputFilesRepository + +class RecordOutputsStepTest extends StepSpec implements FingerprinterFixture { + def outputFilesRepository = Mock(OutputFilesRepository) + def step = new RecordOutputsStep(outputFilesRepository, delegate) + + def outputFile = file("output.txt").text = "output" + def finalOutputs = fingerprintsOf(output: outputFile) + + def context = Mock(Context) + def delegateResult = Mock(CurrentSnapshotResult) + + def "outputs are recorded after execution"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * delegateResult.finalOutputs >> finalOutputs + + then: + 1 * outputFilesRepository.recordOutputs(finalOutputs.values()) + 0 * _ + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ResolveCachingStateStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ResolveCachingStateStepTest.groovy new file mode 100644 index 0000000000000..037f30cdf8751 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ResolveCachingStateStepTest.groovy @@ -0,0 +1,63 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + + +import org.gradle.caching.internal.controller.BuildCacheController +import org.gradle.internal.execution.CachingContext +import org.gradle.internal.execution.IncrementalContext +import org.gradle.internal.execution.Step +import org.gradle.internal.execution.UnitOfWork +import org.gradle.internal.execution.caching.CachingDisabledReason +import org.gradle.internal.execution.caching.CachingDisabledReasonCategory +import spock.lang.Specification + +class ResolveCachingStateStepTest extends Specification { + + def work = Mock(UnitOfWork) + def context = Mock(IncrementalContext) + def buildCache = Mock(BuildCacheController) + def delegateStep = Mock(Step) + + def step = new ResolveCachingStateStep(buildCache, true, delegateStep) + + def "build cache disabled reason is reported when build cache is disabled"() { + when: + step.execute(context) + then: + _ * buildCache.enabled >> false + 1 * context.beforeExecutionState >> Optional.empty() + 1 * delegateStep.execute(_) >> { CachingContext context -> + assert context.cachingState.disabledReasons.get(0).category == CachingDisabledReasonCategory.BUILD_CACHE_DISABLED + } + } + + def "build cache disabled reason is determined without execution state"() { + def disabledReason = new CachingDisabledReason(CachingDisabledReasonCategory.DISABLE_CONDITION_SATISFIED, "Something disabled") + + when: + step.execute(context) + then: + _ * buildCache.enabled >> true + 1 * context.beforeExecutionState >> Optional.empty() + 1 * context.work >> work + 1 * work.shouldDisableCaching() >> Optional.of(disabledReason) + 1 * delegateStep.execute(_) >> { CachingContext context -> + assert context.cachingState.disabledReasons.get(0) == disabledReason + } + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ResolveChangesStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ResolveChangesStepTest.groovy new file mode 100644 index 0000000000000..7e2847c71f6c9 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/ResolveChangesStepTest.groovy @@ -0,0 +1,124 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSortedMap +import org.gradle.internal.execution.CachingContext +import org.gradle.internal.execution.IncrementalChangesContext +import org.gradle.internal.execution.Result +import org.gradle.internal.execution.history.AfterPreviousExecutionState +import org.gradle.internal.execution.history.BeforeExecutionState +import org.gradle.internal.execution.history.changes.ExecutionStateChangeDetector +import org.gradle.internal.execution.history.changes.ExecutionStateChanges + +class ResolveChangesStepTest extends StepSpec { + def changeDetector = Mock(ExecutionStateChangeDetector) + def step = new ResolveChangesStep(changeDetector, delegate) + def context = Mock(CachingContext) + def beforeExecutionState = Mock(BeforeExecutionState) + def delegateResult = Mock(Result) + + def "doesn't provide input file changes when rebuild is forced"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * context.work >> work + 1 * work.requiresInputChanges >> false + 1 * delegate.execute(_) >> { IncrementalChangesContext delegateContext -> + def changes = delegateContext.changes.get() + assert changes.allChangeMessages == ImmutableList.of("Forced rebuild.") + try { + changes.createInputChanges() + assert false + } catch (UnsupportedOperationException e) { + assert e.message == 'Cannot query input changes when input tracking is disabled.' + } + return delegateResult + } + 1 * context.rebuildReason >> Optional.of("Forced rebuild.") + 1 * context.beforeExecutionState >> Optional.empty() + 0 * _ + } + + def "doesn't provide changes when change tracking is disabled"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * context.work >> work + 1 * delegate.execute(_) >> { IncrementalChangesContext delegateContext -> + assert !delegateContext.changes.present + return delegateResult + } + 1 * context.rebuildReason >> Optional.empty() + 1 * context.beforeExecutionState >> Optional.empty() + 0 * _ + } + + def "doesn't provide input file changes when no history is available"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * context.work >> work + 1 * work.requiresInputChanges >> false + 1 * delegate.execute(_) >> { IncrementalChangesContext delegateContext -> + def changes = delegateContext.changes.get() + assert !changes.createInputChanges().incremental + assert changes.allChangeMessages == ImmutableList.of("No history is available.") + return delegateResult + } + 1 * context.rebuildReason >> Optional.empty() + 1 * context.beforeExecutionState >> Optional.of(beforeExecutionState) + 1 * beforeExecutionState.getInputFileProperties() >> ImmutableSortedMap.of() + 1 * context.afterPreviousExecutionState >> Optional.empty() + 0 * _ + } + + def "provides input file changes when history is available"() { + def beforeExecutionState = Mock(BeforeExecutionState) + def afterPreviousExecutionState = Mock(AfterPreviousExecutionState) + def changes = Mock(ExecutionStateChanges) + + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * context.work >> work + 1 * delegate.execute(_) >> { IncrementalChangesContext delegateContext -> + assert delegateContext.changes.get() == changes + return delegateResult + } + 1 * context.rebuildReason >> Optional.empty() + 1 * context.beforeExecutionState >> Optional.of(beforeExecutionState) + 1 * context.afterPreviousExecutionState >> Optional.of(afterPreviousExecutionState) + 1 * work.requiresInputChanges >> false + 1 * work.allowOverlappingOutputs >> true + 1 * changeDetector.detectChanges(afterPreviousExecutionState, beforeExecutionState, work, false, _) >> changes + 0 * _ + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/SkipUpToDateStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/SkipUpToDateStepTest.groovy new file mode 100644 index 0000000000000..765c6de23f43a --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/SkipUpToDateStepTest.groovy @@ -0,0 +1,108 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSortedMap +import org.gradle.caching.internal.origin.OriginMetadata +import org.gradle.internal.Try +import org.gradle.internal.execution.ExecutionOutcome +import org.gradle.internal.execution.IncrementalChangesContext +import org.gradle.internal.execution.SnapshotResult +import org.gradle.internal.execution.history.AfterPreviousExecutionState +import org.gradle.internal.execution.history.changes.ExecutionStateChanges +import org.gradle.internal.fingerprint.impl.EmptyCurrentFileCollectionFingerprint + +class SkipUpToDateStepTest extends StepSpec { + def step = new SkipUpToDateStep(delegate) + def context = Mock(IncrementalChangesContext) + + def changes = Mock(ExecutionStateChanges) + + def "skips when outputs are up to date"() { + when: + def result = step.execute(context) + + then: + result.outcome.get() == ExecutionOutcome.UP_TO_DATE + result.executionReasons.empty + + 1 * context.changes >> Optional.of(changes) + 1 * changes.allChangeMessages >> ImmutableList.of() + 1 * context.afterPreviousExecutionState >> Optional.of(Mock(AfterPreviousExecutionState)) + 0 * _ + } + + def "executes when outputs are not up to date"() { + def delegateResult = Mock(SnapshotResult) + def delegateOutcome = Try.successful(ExecutionOutcome.EXECUTED_NON_INCREMENTALLY) + def delegateOriginMetadata = Mock(OriginMetadata) + def delegateFinalOutputs = ImmutableSortedMap.copyOf([test: EmptyCurrentFileCollectionFingerprint.EMPTY]) + + when: + def result = step.execute(context) + + then: + result.executionReasons == ["change"] + + 1 * context.getWork() >> work + 1 * context.changes >> Optional.of(changes) + 1 * changes.allChangeMessages >> ImmutableList.of("change") + 1 * delegate.execute(context) >> delegateResult + 0 * _ + + when: + def outcome = result.outcome + + then: + outcome == delegateOutcome + + 1 * delegateResult.outcome >> delegateOutcome + 0 * _ + + when: + def originMetadata = result.originMetadata + + then: + originMetadata == delegateOriginMetadata + + 1 * delegateResult.originMetadata >> delegateOriginMetadata + 0 * _ + + when: + def finalOutputs = result.finalOutputs + + then: + finalOutputs == delegateFinalOutputs + + 1 * delegateResult.finalOutputs >> delegateFinalOutputs + 0 * _ + } + + def "executes when change tracking is disabled"() { + when: + def result = step.execute(context) + + then: + result.executionReasons == ["Change tracking is disabled."] + + 1 * context.getWork() >> work + 1 * context.changes >> Optional.empty() + 1 * delegate.execute(context) + 0 * _ + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/StepSpec.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/StepSpec.groovy new file mode 100644 index 0000000000000..f9a0b7d7bac7e --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/StepSpec.groovy @@ -0,0 +1,38 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + + +import org.gradle.internal.execution.Step +import org.gradle.internal.execution.UnitOfWork +import org.gradle.test.fixtures.file.TestFile +import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider +import org.gradle.testing.internal.util.Specification +import org.junit.Rule + +class StepSpec extends Specification { + @Rule + final TestNameTestDirectoryProvider temporaryFolder = TestNameTestDirectoryProvider.newInstance() + + final delegate = Mock(Step) + + final work = Mock(UnitOfWork) + + protected TestFile file(Object... path) { + return temporaryFolder.file(path) + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/StoreSnapshotsStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/StoreSnapshotsStepTest.groovy new file mode 100644 index 0000000000000..40453a67bdfd0 --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/StoreSnapshotsStepTest.groovy @@ -0,0 +1,156 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + +import com.google.common.collect.ImmutableList +import com.google.common.collect.ImmutableSortedMap +import org.gradle.caching.internal.origin.OriginMetadata +import org.gradle.internal.Try +import org.gradle.internal.execution.CurrentSnapshotResult +import org.gradle.internal.execution.ExecutionOutcome +import org.gradle.internal.execution.IncrementalContext +import org.gradle.internal.execution.history.AfterPreviousExecutionState +import org.gradle.internal.execution.history.BeforeExecutionState +import org.gradle.internal.execution.history.ExecutionHistoryStore +import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint +import org.gradle.internal.hash.HashCode +import org.gradle.internal.snapshot.impl.ImplementationSnapshot + +class StoreSnapshotsStepTest extends StepSpec implements FingerprinterFixture { + def executionHistoryStore = Mock(ExecutionHistoryStore) + + def originMetadata = Mock(OriginMetadata) + def implementationSnapshot = ImplementationSnapshot.of("Test", HashCode.fromInt(123)) + def additionalImplementations = ImmutableList.of() + def inputProperties = ImmutableSortedMap.of() + def inputFileProperties = ImmutableSortedMap.of() + def beforeExecutionState = Stub(BeforeExecutionState) { + getImplementation() >> implementationSnapshot + getAdditionalImplementations() >> additionalImplementations + getInputProperties() >> inputProperties + getInputFileProperties() >> inputFileProperties + } + def identity = "identity" + + def outputFile = file("output.txt").text = "output" + def finalOutputs = fingerprintsOf(output: outputFile) + + def context = Mock(IncrementalContext) + def step = new StoreSnapshotsStep(delegate) + def delegateResult = Mock(CurrentSnapshotResult) + + def "output snapshots are stored after successful execution"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * delegateResult.finalOutputs >> finalOutputs + 1 * context.beforeExecutionState >> Optional.of(beforeExecutionState) + 1 * delegateResult.outcome >> Try.successful(ExecutionOutcome.EXECUTED_NON_INCREMENTALLY) + + then: + interaction { expectStore(true, finalOutputs) } + 0 * _ + } + + def "output snapshots are stored after failed execution when there's no previous state available"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * delegateResult.finalOutputs >> finalOutputs + 1 * context.beforeExecutionState >> Optional.of(beforeExecutionState) + 1 * delegateResult.outcome >> Try.failure(new RuntimeException("execution error")) + + then: + 1 * context.afterPreviousExecutionState >> Optional.empty() + + then: + interaction { expectStore(false, finalOutputs) } + 0 * _ + } + + def "output snapshots are stored after failed execution with changed outputs"() { + def afterPreviousExecutionState = Mock(AfterPreviousExecutionState) + + when: + def result = step.execute(context) + + then: + result == delegateResult + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * delegateResult.finalOutputs >> finalOutputs + 1 * context.beforeExecutionState >> Optional.of(beforeExecutionState) + 1 * delegateResult.outcome >> Try.failure(new RuntimeException("execution error")) + + then: + 1 * context.afterPreviousExecutionState >> Optional.of(afterPreviousExecutionState) + 1 * afterPreviousExecutionState.outputFileProperties >> fingerprintsOf([:]) + + then: + interaction { expectStore(false, finalOutputs) } + 0 * _ + } + + def "output snapshots are not stored after failed execution with unchanged outputs"() { + def afterPreviousExecutionState = Mock(AfterPreviousExecutionState) + + when: + def result = step.execute(context) + + then: + result == delegateResult + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * delegateResult.finalOutputs >> finalOutputs + 1 * context.beforeExecutionState >> Optional.of(beforeExecutionState) + 1 * delegateResult.outcome >> Try.failure(new RuntimeException("execution error")) + + then: + 1 * context.afterPreviousExecutionState >> Optional.of(afterPreviousExecutionState) + 1 * afterPreviousExecutionState.outputFileProperties >> finalOutputs + 0 * _ + } + + void expectStore(boolean successful, ImmutableSortedMap finalOutputs) { + 1 * context.work >> work + 1 * work.executionHistoryStore >> executionHistoryStore + 1 * work.identity >> identity + 1 * delegateResult.originMetadata >> originMetadata + 1 * executionHistoryStore.store( + identity, + originMetadata, + implementationSnapshot, + additionalImplementations, + inputProperties, + inputFileProperties, + finalOutputs, + successful + ) + } +} diff --git a/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/TimeoutStepTest.groovy b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/TimeoutStepTest.groovy new file mode 100644 index 0000000000000..d4327ac66478f --- /dev/null +++ b/subprojects/execution/src/test/groovy/org/gradle/internal/execution/steps/TimeoutStepTest.groovy @@ -0,0 +1,112 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.execution.steps + + +import org.gradle.api.InvalidUserDataException +import org.gradle.internal.execution.Context +import org.gradle.internal.execution.Result +import org.gradle.internal.execution.timeout.Timeout +import org.gradle.internal.execution.timeout.TimeoutHandler + +import java.time.Duration +import java.time.temporal.ChronoUnit + +class TimeoutStepTest extends StepSpec { + def timeoutHandler = Mock(TimeoutHandler) + def step = new TimeoutStep(timeoutHandler, delegate) + def context = Mock(Context) + def delegateResult = Mock(Result) + + def "negative timeout is reported"() { + when: + step.execute(context) + + then: + thrown InvalidUserDataException + + 1 * context.work >> work + 1 * work.timeout >> Optional.of(Duration.of(-1, ChronoUnit.SECONDS)) + 1 * work.displayName >> "bad work" + 0 * _ + } + + def "executing without timeout succeeds"() { + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * context.work >> work + 1 * work.timeout >> Optional.empty() + + then: + 1 * delegate.execute(context) >> delegateResult + 0 * _ + } + + def "executing under timeout succeeds"() { + def duration = Duration.of(1, ChronoUnit.SECONDS) + def timeout = Mock(Timeout) + + when: + def result = step.execute(context) + + then: + result == delegateResult + + 1 * context.work >> work + 1 * work.timeout >> Optional.of(duration) + + then: + timeoutHandler.start(_ as Thread, duration) >> timeout + + then: + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * timeout.stop() >> false + 0 * _ + } + + def "executing over timeout fails"() { + def duration = Duration.of(1, ChronoUnit.SECONDS) + def timeout = Mock(Timeout) + + when: + step.execute(context) + + then: + 1 * context.work >> work + 1 * work.timeout >> Optional.of(duration) + + then: + 1 * timeoutHandler.start(_ as Thread, duration) >> timeout + + then: + 1 * delegate.execute(context) >> delegateResult + + then: + 1 * timeout.stop() >> true + + then: + def ex = thrown Exception + ex.message == "Timeout has been exceeded" + 0 * _ + } +} diff --git a/subprojects/files/src/main/java/org/gradle/api/internal/file/FileSystemSubset.java b/subprojects/files/src/main/java/org/gradle/api/internal/file/FileSystemSubset.java index 7f835e8575559..0994237fa0538 100644 --- a/subprojects/files/src/main/java/org/gradle/api/internal/file/FileSystemSubset.java +++ b/subprojects/files/src/main/java/org/gradle/api/internal/file/FileSystemSubset.java @@ -21,7 +21,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.file.DirectoryTree; import org.gradle.api.internal.file.collections.DirectoryTrees; import org.gradle.api.tasks.util.PatternSet; diff --git a/subprojects/files/src/main/java/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollection.java b/subprojects/files/src/main/java/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollection.java index b36d1da9afee8..1fad1f89c6608 100644 --- a/subprojects/files/src/main/java/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollection.java +++ b/subprojects/files/src/main/java/org/gradle/api/internal/file/collections/DefaultConfigurableFileCollection.java @@ -22,9 +22,11 @@ import org.gradle.api.internal.tasks.TaskDependencyResolveContext; import org.gradle.api.internal.tasks.TaskResolver; import org.gradle.internal.file.PathToFileResolver; +import org.gradle.internal.state.Managed; import org.gradle.util.GUtil; import javax.annotation.Nullable; +import java.io.File; import java.util.Arrays; import java.util.Collection; import java.util.LinkedHashSet; @@ -33,7 +35,7 @@ /** * A {@link org.gradle.api.file.FileCollection} which resolves a set of paths relative to a {@link org.gradle.api.internal.file.FileResolver}. */ -public class DefaultConfigurableFileCollection extends CompositeFileCollection implements ConfigurableFileCollection { +public class DefaultConfigurableFileCollection extends CompositeFileCollection implements ConfigurableFileCollection, Managed { private final Set files; private final String displayName; private final PathToFileResolver resolver; @@ -65,6 +67,35 @@ public DefaultConfigurableFileCollection(String displayName, PathToFileResolver buildDependency = new DefaultTaskDependency(taskResolver); } + @Override + public boolean immutable() { + return false; + } + + @Override + public Class publicType() { + return ConfigurableFileCollection.class; + } + + @Override + public Object unpackState() { + return getFiles(); + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Nullable + @Override + public T fromState(Class type, Object state) { + if (!type.isAssignableFrom(ConfigurableFileCollection.class)) { + return null; + } + return type.cast(new DefaultConfigurableFileCollection(resolver, null, (Set) state)); + } + }; + } + @Override public String getDisplayName() { return displayName; diff --git a/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/AbstractXcodeSwiftProjectIntegrationTest.groovy b/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/AbstractXcodeSwiftProjectIntegrationTest.groovy index f951c31b302c0..12ce200469143 100644 --- a/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/AbstractXcodeSwiftProjectIntegrationTest.groovy +++ b/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/AbstractXcodeSwiftProjectIntegrationTest.groovy @@ -20,6 +20,7 @@ import org.gradle.ide.xcode.fixtures.ProjectFile import org.gradle.language.swift.SwiftVersion import org.gradle.nativeplatform.fixtures.app.Swift3 import org.gradle.nativeplatform.fixtures.app.Swift4 +import org.gradle.nativeplatform.fixtures.app.Swift5 import org.gradle.nativeplatform.fixtures.app.SwiftSourceElement import spock.lang.Unroll @@ -49,6 +50,7 @@ abstract class AbstractXcodeSwiftProjectIntegrationTest extends AbstractXcodeNat fixture | sourceCompatibility swift3Component | SwiftVersion.SWIFT3 swift4Component | SwiftVersion.SWIFT4 + swift5Component | SwiftVersion.SWIFT5 } @Unroll @@ -74,6 +76,7 @@ abstract class AbstractXcodeSwiftProjectIntegrationTest extends AbstractXcodeNat fixture | sourceCompatibility swift3Component | SwiftVersion.SWIFT3 swift4Component | SwiftVersion.SWIFT4 + swift5Component | SwiftVersion.SWIFT5 } @Unroll @@ -102,6 +105,7 @@ abstract class AbstractXcodeSwiftProjectIntegrationTest extends AbstractXcodeNat "null" | null "SwiftVersion.SWIFT3" | "3.0" "SwiftVersion.SWIFT4" | "4.0" + "SwiftVersion.SWIFT5" | "5.0" } SwiftSourceElement getSwift3Component() { @@ -112,6 +116,10 @@ abstract class AbstractXcodeSwiftProjectIntegrationTest extends AbstractXcodeNat return new Swift4(rootProjectName) } + SwiftSourceElement getSwift5Component() { + return new Swift5(rootProjectName); + } + void assertHasSwiftVersion(SwiftVersion expectedSwiftVersion, List targets) { assert !targets.empty targets.each { target -> diff --git a/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/AbstractXcodeSwiftWithXCTestProjectIntegrationTest.groovy b/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/AbstractXcodeSwiftWithXCTestProjectIntegrationTest.groovy index 98b933ab51c3d..3aaf070bf609d 100644 --- a/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/AbstractXcodeSwiftWithXCTestProjectIntegrationTest.groovy +++ b/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/AbstractXcodeSwiftWithXCTestProjectIntegrationTest.groovy @@ -21,6 +21,8 @@ import org.gradle.nativeplatform.fixtures.app.Swift3WithSwift4XCTest import org.gradle.nativeplatform.fixtures.app.Swift3WithXCTest import org.gradle.nativeplatform.fixtures.app.Swift4WithSwift3XCTest import org.gradle.nativeplatform.fixtures.app.Swift4WithXCTest +import org.gradle.nativeplatform.fixtures.app.Swift5WithSwift4XCTest +import org.gradle.nativeplatform.fixtures.app.Swift5WithXCTest import org.gradle.nativeplatform.fixtures.app.SwiftSourceElement import spock.lang.Unroll @@ -35,6 +37,11 @@ abstract class AbstractXcodeSwiftWithXCTestProjectIntegrationTest extends Abstra return new Swift4WithXCTest(rootProjectName) } + @Override + SwiftSourceElement getSwift5Component() { + return new Swift5WithXCTest(rootProjectName) + } + @Override String getComponentUnderTestDsl() { return "xctest" @@ -65,6 +72,8 @@ abstract class AbstractXcodeSwiftWithXCTestProjectIntegrationTest extends Abstra @Unroll def "honors Swift source compatibility difference on both tested component (#componentSourceCompatibility) and XCTest component (#xctestSourceCompatibility)"() { given: + // TODO: Generating the Xcode files for incompatible source compatibility shouldn't fail the build + // Thus, we should be able to remove the assumption below. assumeSwiftCompilerSupportsLanguageVersion(componentSourceCompatibility) fixture.writeToProject(testDirectory) makeSingleProject() @@ -85,5 +94,6 @@ abstract class AbstractXcodeSwiftWithXCTestProjectIntegrationTest extends Abstra fixture | componentSourceCompatibility | xctestSourceCompatibility new Swift3WithSwift4XCTest(rootProjectName) | SwiftVersion.SWIFT3 | SwiftVersion.SWIFT4 new Swift4WithSwift3XCTest(rootProjectName) | SwiftVersion.SWIFT4 | SwiftVersion.SWIFT3 + new Swift5WithSwift4XCTest(rootProjectName) | SwiftVersion.SWIFT5 | SwiftVersion.SWIFT4 } } diff --git a/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/fixtures/AbstractXcodeIntegrationSpec.groovy b/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/fixtures/AbstractXcodeIntegrationSpec.groovy index 2b43f04903c96..4f84c3a7ca7dc 100644 --- a/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/fixtures/AbstractXcodeIntegrationSpec.groovy +++ b/subprojects/ide-native/src/integTest/groovy/org/gradle/ide/xcode/fixtures/AbstractXcodeIntegrationSpec.groovy @@ -146,7 +146,7 @@ rootProject.name = "${rootProjectName}" // TODO: Use @RequiresInstalledToolChain instead once Xcode test are sorted out void assumeSwiftCompilerSupportsLanguageVersion(SwiftVersion swiftVersion) { assert toolChain != null, "You need to specify Swift tool chain requirement with 'requireSwiftToolChain()'" - assumeTrue(toolChain.version.major >= swiftVersion.version) + assumeTrue((toolChain.version.major == 5 && swiftVersion.version in [5, 4]) || (toolChain.version.major == 4 && swiftVersion.version in [4, 3]) || (toolChain.version.major == 3 && swiftVersion.version == 3)) } void assertTargetIsUnitTest(ProjectFile.PBXTarget target, String expectedProductName) { diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/common/build.gradle b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/common/build.gradle index cdbc5a914a398..93eefc7b2af6d 100644 --- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/common/build.gradle +++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/common/build.gradle @@ -1,6 +1,6 @@ apply plugin: 'java' -sourceCompatibility = 1.7 +sourceCompatibility = 1.6 configurations { provided diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonJdt.properties b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonJdt.properties index 7341ab1683c4f..8000cd6ca6142 100644 --- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonJdt.properties +++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonJdt.properties @@ -1,11 +1,11 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.7 +org.eclipse.jdt.core.compiler.compliance=1.6 org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.source=1.7 +org.eclipse.jdt.core.compiler.source=1.6 diff --git a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonWtpFacet.xml b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonWtpFacet.xml index b14a4dc410ccb..fbb5eacc4d7fd 100644 --- a/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonWtpFacet.xml +++ b/subprojects/ide/src/integTest/resources/org/gradle/plugins/ide/eclipse/EclipseIntegrationTest/canCreateAndDeleteMetaData/expectedFiles/commonWtpFacet.xml @@ -1,5 +1,5 @@ - + diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/eclipse/EclipsePlugin.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/eclipse/EclipsePlugin.java index b100560ade406..4ed24b6b650fa 100644 --- a/subprojects/ide/src/main/java/org/gradle/plugins/ide/eclipse/EclipsePlugin.java +++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/eclipse/EclipsePlugin.java @@ -95,7 +95,7 @@ protected void onApply(Project project) { getLifecycleTask().configure(withDescription("Generates all Eclipse files.")); getCleanTask().configure(withDescription("Cleans all Eclipse files.")); - EclipseModel model = project.getExtensions().create("eclipse", EclipseModel.class); + EclipseModel model = project.getExtensions().create("eclipse", EclipseModel.class, project); configureEclipseProject((ProjectInternal) project, model); configureEclipseJdt(project, model); diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/eclipse/model/EclipseModel.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/eclipse/model/EclipseModel.java index 50d12f9ec1367..1d4acff08a33a 100644 --- a/subprojects/ide/src/main/java/org/gradle/plugins/ide/eclipse/model/EclipseModel.java +++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/eclipse/model/EclipseModel.java @@ -19,7 +19,12 @@ import com.google.common.base.Preconditions; import groovy.lang.Closure; import org.gradle.api.Action; +import org.gradle.api.Incubating; +import org.gradle.api.Project; +import org.gradle.api.internal.project.ProjectInternal; +import org.gradle.api.internal.tasks.DefaultTaskDependency; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.tasks.TaskDependency; import org.gradle.internal.xml.XmlTransformer; import org.gradle.plugins.ide.api.XmlFileContentMerger; @@ -67,6 +72,26 @@ public class EclipseModel { private EclipseWtp wtp; + private final DefaultTaskDependency synchronizationTasks; + + private final DefaultTaskDependency autoBuildTasks; + + public EclipseModel() { + synchronizationTasks = new DefaultTaskDependency(); + autoBuildTasks = new DefaultTaskDependency(); + } + + /** + * Constructor. + * + * @since 5.4 + */ + @Incubating + public EclipseModel(Project project) { + this.synchronizationTasks = new DefaultTaskDependency(((ProjectInternal) project).getTasks()); + this.autoBuildTasks = new DefaultTaskDependency(((ProjectInternal) project).getTasks()); + } + /** * Injects and returns an instance of {@link ObjectFactory}. * @@ -217,6 +242,57 @@ public void jdt(Action action) { action.execute(getJdt()); } + /** + * Returns the tasks to be executed before the Eclipse synchronization starts. + *

    + * This property doesn't have a direct effect to the Gradle Eclipse plugin's behaviour. It is used, however, by + * Buildship to execute the configured tasks each time before the user imports the project or before a project + * synchronization starts. + * + * @return the tasks names + * @since 5.4 + */ + @Incubating + public TaskDependency getSynchronizationTasks() { + return synchronizationTasks; + } + + /** + * Set tasks to be executed before the Eclipse synchronization. + * + * @see #getSynchronizationTasks() + * @since 5.4 + */ + @Incubating + public void synchronizationTasks(Object... synchronizationTasks) { + this.synchronizationTasks.add(synchronizationTasks); + } + + /** + * Returns the tasks to be executed during the Eclipse auto-build. + *

    + * This property doesn't have a direct effect to the Gradle Eclipse plugin's behaviour. It is used, however, by + * Buildship to execute the configured tasks each time when the Eclipse automatic build is triggered for the project. + * + * @return the tasks names + * @since 5.4 + */ + @Incubating + public TaskDependency getAutoBuildTasks() { + return autoBuildTasks; + } + + /** + * Set tasks to be executed during the Eclipse auto-build. + * + * @see #getAutoBuildTasks() + * @since 5.4 + */ + @Incubating + public void autoBuildTasks(Object... autoBuildTasks) { + this.autoBuildTasks.add(autoBuildTasks); + } + /** * Adds path variables to be used for replacing absolute paths in classpath entries. *

    diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/RunEclipseTasksBuilder.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/RunEclipseTasksBuilder.java new file mode 100644 index 0000000000000..c0678b1de1136 --- /dev/null +++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/RunEclipseTasksBuilder.java @@ -0,0 +1,88 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.plugins.ide.internal.tooling; + +import org.gradle.StartParameter; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.plugins.ide.eclipse.model.EclipseModel; +import org.gradle.tooling.provider.model.ToolingModelBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class RunEclipseTasksBuilder implements ToolingModelBuilder { + + @Override + public boolean canBuild(String modelName) { + return isSyncModel(modelName) || isAutoBuildModel(modelName); + } + + @Override + public Object buildAll(String modelName, Project project) { + StartParameter startParameter = project.getGradle().getStartParameter(); + List taskPaths = new ArrayList(); + taskPaths.addAll(startParameter.getTaskNames()); + + boolean isSyncModel = isSyncModel(modelName); + boolean isAutoBuildModel = isAutoBuildModel(modelName); + + for (Project p : project.getAllprojects()) { + EclipseModel model = p.getExtensions().findByType(EclipseModel.class); + if (model != null) { + if (isSyncModel) { + for (Task t : model.getSynchronizationTasks().getDependencies(null)) { + taskPaths.add(t.getPath()); + } + } + if (isAutoBuildModel) { + for (Task t : model.getAutoBuildTasks().getDependencies(null)) { + taskPaths.add(t.getPath()); + } + } + } + } + + if (taskPaths.isEmpty()) { + // If no tasks is specified then the default tasks will be executed. + // To work around this, we assign a new empty task for execution. + String placeHolderTaskName = placeHolderTaskName(project, "nothing"); + project.task(placeHolderTaskName); + taskPaths.add(placeHolderTaskName); + } + + project.getGradle().getStartParameter().setTaskNames(taskPaths); + return null; + } + + private static boolean isSyncModel(String modelName) { + return modelName.equals("org.gradle.tooling.model.eclipse.RunEclipseSynchronizationTasks"); + } + + + private static boolean isAutoBuildModel(String modelName) { + return modelName.equals("org.gradle.tooling.model.eclipse.RunEclipseAutoBuildTasks"); + } + + private static String placeHolderTaskName(Project project, String baseName) { + if (project.getTasks().findByName(baseName) == null) { + return baseName; + } else { + return placeHolderTaskName(project, baseName + "_"); + } + } +} diff --git a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/ToolingModelServices.java b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/ToolingModelServices.java index 165704307ea5c..aa2754484a0f5 100644 --- a/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/ToolingModelServices.java +++ b/subprojects/ide/src/main/java/org/gradle/plugins/ide/internal/tooling/ToolingModelServices.java @@ -51,6 +51,7 @@ protected BuildScopeToolingModelBuilderRegistryAction createIdeBuildScopeTooling public void execute(ToolingModelBuilderRegistry registry) { GradleProjectBuilder gradleProjectBuilder = new GradleProjectBuilder(); IdeaModelBuilder ideaModelBuilder = new IdeaModelBuilder(gradleProjectBuilder, services); + registry.register(new RunEclipseTasksBuilder()); registry.register(new EclipseModelBuilder(gradleProjectBuilder, services)); registry.register(ideaModelBuilder); registry.register(gradleProjectBuilder); diff --git a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy index 6b1806cf8508a..d8069cb7fe796 100644 --- a/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy +++ b/subprojects/ide/src/test/groovy/org/gradle/plugins/ide/eclipse/model/EclipseModelTest.groovy @@ -20,6 +20,7 @@ import org.gradle.api.Action import org.gradle.api.JavaVersion import org.gradle.api.XmlProvider import org.gradle.api.internal.PropertiesTransformer +import org.gradle.api.internal.project.ProjectInternal import org.gradle.internal.xml.XmlTransformer import org.gradle.plugins.ide.api.PropertiesFileContentMerger import org.gradle.plugins.ide.api.XmlFileContentMerger @@ -27,7 +28,7 @@ import spock.lang.Specification class EclipseModelTest extends Specification { - EclipseModel model = new EclipseModel() + EclipseModel model = new EclipseModel(Mock(ProjectInternal)) def setup() { model.classpath = new EclipseClasspath(null) diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy index 615613c94396e..d60b422dd522b 100644 --- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy +++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/BuildSourceBuilderIntegrationTest.groovy @@ -18,95 +18,102 @@ package org.gradle.integtests import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.timeout.IntegrationTestTimeout +import org.gradle.internal.nativeintegration.ProcessEnvironment +import org.gradle.internal.operations.BuildOperationDescriptor +import org.gradle.internal.operations.BuildOperationListener +import org.gradle.internal.operations.BuildOperationListenerManager +import org.gradle.internal.operations.OperationFinishEvent +import org.gradle.internal.operations.OperationIdentifier +import org.gradle.internal.operations.OperationProgressEvent +import org.gradle.internal.operations.OperationStartEvent +import org.gradle.internal.time.Time +import org.gradle.internal.time.Timer import org.gradle.test.fixtures.file.TestFile +import org.gradle.test.fixtures.server.http.BlockingHttpServer +import org.junit.Rule import spock.lang.Issue @IntegrationTestTimeout(600) class BuildSourceBuilderIntegrationTest extends AbstractIntegrationSpec { + @Rule + BlockingHttpServer server = new BlockingHttpServer() @Issue("https://issues.gradle.org/browse/GRADLE-2032") def "can simultaneously run gradle on projects with buildSrc"() { - given: - def buildSrcDir = file("buildSrc").createDir() - writeSharedClassFile(buildSrcDir) - file('buildSrc/build.gradle').text = ''' - tasks.all { - doFirst { - println "${name} started at ${new Date().time}" + def initScript = file("init.gradle") + initScript << """ + import ${BuildOperationListenerManager.name} + import ${BuildOperationListener.name} + import ${BuildOperationDescriptor.name} + import ${OperationStartEvent.name} + import ${OperationProgressEvent.name} + import ${OperationFinishEvent.name} + import ${OperationIdentifier.name} + import ${ProcessEnvironment.name} + import ${Time.name} + import ${Timer.name} + + def pid = gradle.services.get(ProcessEnvironment).maybeGetPid() + def timer = Time.startTimer() + + def listener = new TraceListener(pid: pid, timer: timer) + def manager = gradle.services.get(BuildOperationListenerManager) + manager.addListener(listener) + gradle.buildFinished { manager.removeListener(listener) } + + class TraceListener implements BuildOperationListener { + Long pid + Timer timer + + void started(BuildOperationDescriptor buildOperation, OperationStartEvent startEvent) { + println("[\$pid] [\$timer.elapsed] start " + buildOperation.displayName) + } + + void progress(OperationIdentifier operationIdentifier, OperationProgressEvent progressEvent) { } - doLast { - println "${name} finished at ${new Date().time}" + + void finished(BuildOperationDescriptor buildOperation, OperationFinishEvent finishEvent) { } } - ''' + """ + server.start() + + given: + def buildSrcDir = file("buildSrc").createDir() + writeSharedClassFile(buildSrcDir) buildFile.text = """ import org.gradle.integtest.test.BuildSrcTask - int MAX_LOOP_COUNT = java.util.concurrent.TimeUnit.MINUTES.toMillis(5) / 10 - task blocking(type:BuildSrcTask) { + task build1(type:BuildSrcTask) { doLast { - file("run1washere.lock").createNewFile() - - int count = 0 - while(!file("run2washere.lock").exists() && count++ < MAX_LOOP_COUNT){ - sleep 10 - } + ${server.callFromBuild('build1')} } } - task releasing(type:BuildSrcTask) { + task build2(type:BuildSrcTask) { doLast { - int count = 0 - while(!file("run1washere.lock").exists() && count++ < MAX_LOOP_COUNT){ - sleep 10 - } - file("run2washere.lock").createNewFile() + ${server.callFromBuild('build2')} } } """ + + server.expectConcurrent("build1", "build2") + when: - def runBlockingHandle = executer.withTasks("blocking").start() - def runReleaseHandle = executer.withTasks("releasing").start() + def runBlockingHandle = executer.withTasks("build1").usingInitScript(initScript).start() + def runReleaseHandle = executer.withTasks("build2").usingInitScript(initScript).start() + and: def releaseResult = runReleaseHandle.waitForFinish() def blockingResult = runBlockingHandle.waitForFinish() - then: - blockingResult.assertTasksExecuted(":blocking") - releaseResult.assertTasksExecuted(":releasing") - - def blockingTaskTimes = finishedTaskTimes(blockingResult.output) - def releasingTaskTimes = finishedTaskTimes(releaseResult.output) - - def blockingBuildSrcBuiltFirst = blockingTaskTimes.values().min() < releasingTaskTimes.values().min() - def (firstBuildResult, secondBuildResult) = blockingBuildSrcBuiltFirst ? [blockingResult, releaseResult] : [releaseResult, blockingResult] - def lastTaskTimeFromFirstBuildSrcBuild = finishedTaskTimes(firstBuildResult.output).values().max() - def firstTaskTimeFromSecondBuildSrcBuild = startedTaskTimes(secondBuildResult.output).values().min() - - lastTaskTimeFromFirstBuildSrcBuild < firstTaskTimeFromSecondBuildSrcBuild + then: + blockingResult.assertTasksExecuted(":build1") + releaseResult.assertTasksExecuted(":build2") cleanup: - runReleaseHandle.abort() - runBlockingHandle.abort() - } - - Map startedTaskTimes(String output) { - taskTimes(output, 'started') - } - - Map finishedTaskTimes(String output) { - taskTimes(output, 'finished') - } - - Map taskTimes(String output, String state) { - output.readLines().collect { - it =~ /(.*) ${state} at (\d*)/ - }.findAll { - it.matches() - }.collectEntries { - def match = it[0] - [(match[1]): Long.parseLong(match[2])] - } + runReleaseHandle?.abort() + runBlockingHandle?.abort() } void writeSharedClassFile(TestFile targetDirectory) { diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy index eb933706487bd..08e84ab424954 100644 --- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy +++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/SyncTaskIntegrationTest.groovy @@ -17,6 +17,8 @@ package org.gradle.integtests import groovy.transform.NotYetImplemented import org.gradle.integtests.fixtures.AbstractIntegrationSpec +import org.gradle.util.Requires +import org.gradle.util.TestPrecondition class SyncTaskIntegrationTest extends AbstractIntegrationSpec { @@ -362,6 +364,34 @@ class SyncTaskIntegrationTest extends AbstractIntegrationSpec { !file('dest/extra.txt').exists() } + @Requires(TestPrecondition.WINDOWS) + def "sync fails when unable to clean-up files"() { + given: + file('source').create { + file 'file1.txt' + file 'file2.txt' + } + file('dest').create { + file 'extra.txt' + } + // Intentionally hold open a file + def ins = new FileInputStream(file("dest/extra.txt")) + buildScript ''' + task syncIt { + doLast { + project.sync { + from 'source' + into 'dest' + } + } + } + '''.stripIndent() + + expect: + fails 'syncIt' + ins.close() + } + def "sync from file tree"() { given: file('source').create { diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesIntegrationTest.groovy index 85b3fa366d260..4c7a3dd50bd38 100644 --- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesIntegrationTest.groovy +++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/UserGuideSamplesIntegrationTest.groovy @@ -20,6 +20,7 @@ import org.gradle.cache.internal.DefaultGeneratedGradleJarCache import org.gradle.integtests.fixtures.executer.IntegrationTestBuildContext import org.gradle.integtests.fixtures.executer.MoreMemorySampleModifier import org.gradle.integtests.fixtures.logging.ArtifactResolutionOmittingOutputNormalizer +import org.gradle.integtests.fixtures.logging.DependencyInsightOutputNormalizer import org.gradle.integtests.fixtures.logging.NativeComponentReportOutputNormalizer import org.gradle.integtests.fixtures.logging.PlayComponentReportOutputNormalizer import org.gradle.integtests.fixtures.logging.SampleOutputNormalizer @@ -39,12 +40,13 @@ import org.junit.runner.RunWith @Requires(TestPrecondition.JDK8_OR_LATER) @RunWith(GradleSamplesRunner.class) @SamplesOutputNormalizers([ - JavaObjectSerializationOutputNormalizer.class, - SampleOutputNormalizer.class, - FileSeparatorOutputNormalizer.class, - ArtifactResolutionOmittingOutputNormalizer.class, - NativeComponentReportOutputNormalizer.class, - PlayComponentReportOutputNormalizer.class + JavaObjectSerializationOutputNormalizer, + SampleOutputNormalizer, + FileSeparatorOutputNormalizer, + ArtifactResolutionOmittingOutputNormalizer, + NativeComponentReportOutputNormalizer, + PlayComponentReportOutputNormalizer, + DependencyInsightOutputNormalizer ]) @SampleModifiers([SetMirrorsSampleModifier, MoreMemorySampleModifier]) class UserGuideSamplesIntegrationTest { diff --git a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/dependencymanagement/SamplesTroubleshootingDependencyResolutionIntegrationTest.groovy b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/dependencymanagement/SamplesTroubleshootingDependencyResolutionIntegrationTest.groovy index 0f534430ae7ed..bb59d410d66bb 100644 --- a/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/dependencymanagement/SamplesTroubleshootingDependencyResolutionIntegrationTest.groovy +++ b/subprojects/integ-test/src/integTest/groovy/org/gradle/integtests/samples/dependencymanagement/SamplesTroubleshootingDependencyResolutionIntegrationTest.groovy @@ -31,7 +31,7 @@ class SamplesTroubleshootingDependencyResolutionIntegrationTest extends Abstract @Unroll @UsesSample("userguide/dependencyManagement/troubleshooting/cache/changing") - def "can declare custom TTL for dependency with dynamic version"() { + def "can declare custom TTL for dependency with changing version"() { given: def sampleDir = sample.dir.file(dsl) @@ -48,8 +48,8 @@ class SamplesTroubleshootingDependencyResolutionIntegrationTest extends Abstract } @Unroll - @UsesSample("userguide/dependencyManagement/troubleshooting/cache/changing") - def "can declare custom TTL for dependency with changing version"() { + @UsesSample("userguide/dependencyManagement/troubleshooting/cache/dynamic") + def "can declare custom TTL for dependency with dynamic version"() { given: def sampleDir = sample.dir.file(dsl) diff --git a/subprojects/internal-integ-testing/internal-integ-testing.gradle.kts b/subprojects/internal-integ-testing/internal-integ-testing.gradle.kts index 562fe01c9781d..72a4817a04484 100644 --- a/subprojects/internal-integ-testing/internal-integ-testing.gradle.kts +++ b/subprojects/internal-integ-testing/internal-integ-testing.gradle.kts @@ -44,7 +44,7 @@ dependencies { compile(library("jsch")) compile(library("jcifs")) compile(library("jansi")) - compile(library("commons_collections")) + compile(library("ansi_control_sequence_util")) compile("org.apache.mina:mina-core") compile(testLibrary("sampleCheck")) { exclude(module = "groovy-all") diff --git a/subprojects/internal-integ-testing/src/integTest/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuterIntegrationTest.groovy b/subprojects/internal-integ-testing/src/integTest/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuterIntegrationTest.groovy index 43fdb95222c71..9326527a18ab1 100644 --- a/subprojects/internal-integ-testing/src/integTest/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuterIntegrationTest.groovy +++ b/subprojects/internal-integ-testing/src/integTest/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuterIntegrationTest.groovy @@ -23,8 +23,6 @@ import org.junit.Rule import spock.lang.Specification import spock.lang.Unroll -import static org.gradle.util.TextUtil.normaliseLineSeparators - class InProcessGradleExecuterIntegrationTest extends Specification { @Rule RedirectStdOutAndErr outputs = new RedirectStdOutAndErr() @@ -146,6 +144,6 @@ class InProcessGradleExecuterIntegrationTest extends Specification { } def stripped(String output) { - return normaliseLineSeparators(LogContent.stripWorkInProgressArea(output)) + return LogContent.of(output).withNormalizedEol() } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CompilationOutputsFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CompilationOutputsFixture.groovy index 3e5c7101bf0fc..5e3759c935218 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CompilationOutputsFixture.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/CompilationOutputsFixture.groovy @@ -105,6 +105,13 @@ class CompilationOutputsFixture { assert changedFileNames == asSet(classNames) } + //asserts files deleted since last snapshot. + void deletedFiles(String... fileNames) { + def expectedNames = fileNames.collect({ removeExtension(it) }) as Set + def deleted = snapshot.findAll { !it.exists() }.collect { removeExtension(it.name) } as Set + assert deleted == expectedNames + } + //asserts classes deleted since last snapshot. Class means file name without extension. void deletedClasses(String... classNames) { def deleted = snapshot.findAll { !it.exists() }.collect { removeExtension(it.name) } as Set diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExperimentalIncrementalArtifactTransformationsRunner.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExperimentalIncrementalArtifactTransformationsRunner.groovy deleted file mode 100644 index d3c350e8a0acd..0000000000000 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/ExperimentalIncrementalArtifactTransformationsRunner.groovy +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.integtests.fixtures - -import groovy.transform.CompileStatic -import org.gradle.test.fixtures.file.TestFile - -@CompileStatic -class ExperimentalIncrementalArtifactTransformationsRunner extends BehindFlagFeatureRunner { - public final static String INCREMENTAL_ARTIFACT_TRANSFORMATIONS = "org.gradle.internal.artifacts.transforms.testWithTransformerWorkspaces" - - ExperimentalIncrementalArtifactTransformationsRunner(Class target) { - super(target, [ - (INCREMENTAL_ARTIFACT_TRANSFORMATIONS): booleanFeature("project workspaces for transformations") - ]) - } - - static boolean isIncrementalArtifactTransformations() { - Boolean.getBoolean(INCREMENTAL_ARTIFACT_TRANSFORMATIONS) - } - - static void configureIncrementalArtifactTransformations(TestFile settingsFile) { - if (incrementalArtifactTransformations) { - FeaturePreviewsFixture.enableIncrementalArtifactTransformations(settingsFile) - } - } -} diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/FeaturePreviewsFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/FeaturePreviewsFixture.groovy index 3fff644e52cd7..2f466e824a01e 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/FeaturePreviewsFixture.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/FeaturePreviewsFixture.groovy @@ -21,7 +21,7 @@ import org.gradle.api.internal.FeaturePreviews.Feature class FeaturePreviewsFixture { static def activeFeatures() { - EnumSet.of(Feature.GRADLE_METADATA, Feature.INCREMENTAL_ARTIFACT_TRANSFORMATIONS) + EnumSet.of(Feature.GRADLE_METADATA) } static def inactiveFeatures() { @@ -33,12 +33,6 @@ class FeaturePreviewsFixture { static void enableGradleMetadata(File settings) { settings << """ enableFeaturePreview("GRADLE_METADATA") -""" - } - - static void enableIncrementalArtifactTransformations(File settings) { - settings << """ -enableFeaturePreview("INCREMENTAL_ARTIFACT_TRANSFORMATIONS") """ } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RetryConditions.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RetryConditions.groovy index d54818091699d..d836db30f13d0 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RetryConditions.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RetryConditions.groovy @@ -17,9 +17,12 @@ package org.gradle.integtests.fixtures import org.gradle.api.JavaVersion +import org.gradle.integtests.fixtures.daemon.DaemonLogsAnalyzer import org.gradle.util.GradleVersion import org.gradle.util.TestPrecondition +import javax.annotation.Nullable + class RetryConditions { static private final String[] FILES_TO_PRESERVE = ['reproducible-archives-init.gradle'] @@ -66,7 +69,7 @@ class RetryConditions { return shouldRetry(specification, failure, daemonsFixture) } - private static boolean shouldRetry(Object specification, Throwable failure, daemonsFixture) { + private static boolean shouldRetry(Object specification, Throwable failure, @Nullable DaemonLogsAnalyzer daemonsFixture) { String releasedGradleVersion = specification.hasProperty("releasedGradleVersion") ? specification.releasedGradleVersion : null def caughtGradleConnectionException = specification.hasProperty("caughtGradleConnectionException") ? specification.caughtGradleConnectionException : null @@ -129,6 +132,19 @@ class RetryConditions { return cleanProjectDir(specification) } + // known problem with Gradle versions < 3.5 + // See https://github.com/gradle/gradle-private/issues/744 + if (targetDistVersion < GradleVersion.version('3.5') && daemonsFixture != null && getRootCauseMessage(failure) == 'Build cancelled.') { + for (daemon in daemonsFixture.daemons) { + if (daemon.log.contains('Could not receive message from client.') + && daemon.log.contains('java.lang.NullPointerException') + && daemon.log.contains('org.gradle.launcher.daemon.server.exec.LogToClient')) { + println "Retrying test because the dispatcher was not ready for receiving a log event. Check log of daemon with PID ${daemon.context.pid}." + return cleanProjectDir(specification) + } + } + } + // sometime sockets are unexpectedly disappearing on daemon side (running on windows): https://github.com/gradle/gradle/issues/1111 didSocketDisappearOnWindows(failure, specification, daemonsFixture, targetDistVersion >= GradleVersion.version('3.0')) } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RichConsoleStyling.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RichConsoleStyling.groovy index 4925a6f932e45..58938734911eb 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RichConsoleStyling.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/RichConsoleStyling.groovy @@ -16,7 +16,8 @@ package org.gradle.integtests.fixtures -import org.fusesource.jansi.Ansi +import org.gradle.integtests.fixtures.executer.GradleHandle +import org.gradle.integtests.fixtures.executer.LogContent /** * A trait for testing console behavior. @@ -24,45 +25,11 @@ import org.fusesource.jansi.Ansi * Note: The console output contains formatting characters. */ trait RichConsoleStyling { - public final static String CONTROL_SEQUENCE_START = "\u001B[" - public final static String CONTROL_SEQUENCE_SEPARATOR = ";" - public final static String CONTROL_SEQUENCE_END = "m" - public final static String DEFAULT_TEXT = "0;39" - - /** - * Wraps the text in the proper control characters for styled output in the rich console - */ - static String styledText(String plainText, Ansi.Color color, Ansi.Attribute... attributes) { - String styledString = CONTROL_SEQUENCE_START - if (color) { - styledString += color.fg() - } - if (attributes) { - attributes.each { attribute -> - if (styledString.length() > CONTROL_SEQUENCE_START.length()) { - styledString += CONTROL_SEQUENCE_SEPARATOR - } - styledString += attribute.value() - } - } - styledString += CONTROL_SEQUENCE_END + plainText + CONTROL_SEQUENCE_START - if (color) { - styledString += DEFAULT_TEXT - } - styledString += CONTROL_SEQUENCE_END - - return styledString - } - static String workInProgressLine(String plainText) { - return boldOn() + plainText + reset() - } - - private static String boldOn() { - "${CONTROL_SEQUENCE_START}1m" + return "{bold-on}" + plainText + "{bold-off}" } - private static String reset() { - "${CONTROL_SEQUENCE_START}m" + static void assertHasWorkInProgress(GradleHandle handle, String plainText) { + assert LogContent.of(handle.standardOutput).ansiCharsToColorText().withNormalizedEol().contains(workInProgressLine(plainText)) } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/console/AbstractConsoleGroupedTaskFunctionalTest.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/console/AbstractConsoleGroupedTaskFunctionalTest.groovy index aecce379bb5cc..a8996e678c3f2 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/console/AbstractConsoleGroupedTaskFunctionalTest.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/console/AbstractConsoleGroupedTaskFunctionalTest.groovy @@ -38,35 +38,91 @@ abstract class AbstractConsoleGroupedTaskFunctionalTest extends AbstractIntegrat } boolean errorsShouldAppearOnStdout() { - // If stderr is attached to the console or if we'll use the fallback console - return (consoleAttachment.isStderrAttached() && consoleAttachment.isStdoutAttached()) || usesFallbackConsole() + // If both stdout and stderr is attached to the console, they are merged together + return consoleAttachment.isStderrAttached() && consoleAttachment.isStdoutAttached() } - boolean usesFallbackConsole() { - return consoleAttachment == ConsoleAttachment.NOT_ATTACHED && (consoleType == ConsoleOutput.Rich || consoleType == ConsoleOutput.Verbose) + boolean stdoutUsesStyledText() { + return consoleType == ConsoleOutput.Rich || consoleType == ConsoleOutput.Verbose || consoleType == ConsoleOutput.Auto && consoleAttachment.stdoutAttached + } + + boolean stderrUsesStyledText() { + return consoleType == ConsoleOutput.Rich || consoleType == ConsoleOutput.Verbose || consoleType == ConsoleOutput.Auto && consoleAttachment.stderrAttached } abstract ConsoleOutput getConsoleType() - protected StyledOutput styled(String plainOutput, Ansi.Color color, Ansi.Attribute... attributes) { - return new StyledOutput(plainOutput, color, attributes) + protected StyledOutput styled(Ansi.Color color, Ansi.Attribute attribute) { + return new StyledOutput(null, color, attribute) + } + + protected StyledOutput styled(Ansi.Attribute attribute) { + return new StyledOutput(null, null, attribute) } - public class StyledOutput { - private final String plainOutput - private final String styledOutput + class StyledOutput { + final String plainOutput + final String styledOutput + private final Ansi.Color color + private final Ansi.Attribute attribute + + private StyledOutput(StyledOutput previous, Ansi.Color color, Ansi.Attribute attribute) { + if (attribute != Ansi.Attribute.INTENSITY_BOLD && attribute != null) { + throw new UnsupportedOperationException() + } + this.color = color + this.attribute = attribute + def previousColor = null + def previousAttribute = null + def styled = "" + if (previous == null) { + this.plainOutput = "" + } else { + this.plainOutput = previous.plainOutput + previousColor = previous.color + previousAttribute = previous.attribute + styled = previous.styledOutput + } + if (attribute != null && previousAttribute == null) { + styled += "{bold-on}" + } + if (attribute == null && previousAttribute != null) { + styled += "{bold-off}" + } + if (color != null && color != previousColor) { + styled += "{foreground-color " + color.name().toLowerCase() + "}" + } + if (color == null && previousColor != null) { + styled += "{foreground-color default}" + } + this.styledOutput = styled + } + + private StyledOutput(StyledOutput previous, String text) { + this.color = previous.color + this.attribute = previous.attribute + this.plainOutput = previous.plainOutput + text + this.styledOutput = previous.styledOutput + text + } + + StyledOutput text(String text) { + return new StyledOutput(this, text) + } + + StyledOutput styled(Ansi.Attribute attribute) { + return new StyledOutput(this, color, attribute) + } - StyledOutput(String plainOutput, Ansi.Color color, Ansi.Attribute... attributes) { - this.plainOutput = plainOutput - this.styledOutput = styledText(plainOutput, color, attributes) + StyledOutput off() { + return new StyledOutput(this, null, null) } - public String getOutput() { - return consoleAttachment.stdoutAttached ? styledOutput : plainOutput + String getOutput() { + return stdoutUsesStyledText() ? styledOutput : plainOutput } - public String getErrorOutput() { - return consoleAttachment.stderrAttached ? styledOutput : plainOutput + String getErrorOutput() { + return stderrUsesStyledText() ? styledOutput : plainOutput } } } diff --git a/subprojects/soak/src/testFixtures/groovy/org/gradle/launcher/daemon/fixtures/JavaGarbageCollector.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/JavaGarbageCollector.groovy similarity index 59% rename from subprojects/soak/src/testFixtures/groovy/org/gradle/launcher/daemon/fixtures/JavaGarbageCollector.groovy rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/JavaGarbageCollector.groovy index 1862aefbb0dc7..f522733f60883 100644 --- a/subprojects/soak/src/testFixtures/groovy/org/gradle/launcher/daemon/fixtures/JavaGarbageCollector.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/daemon/JavaGarbageCollector.groovy @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ * limitations under the License. */ -package org.gradle.launcher.daemon.fixtures; +package org.gradle.integtests.fixtures.daemon -public enum JavaGarbageCollector { +enum JavaGarbageCollector { ORACLE_PARALLEL_CMS("-XX:+UseConcMarkSweepGC"), ORACLE_SERIAL9("-XX:+UseSerialGC"), ORACLE_G1("-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC"), @@ -29,21 +29,7 @@ public enum JavaGarbageCollector { this.jvmArgs = jvmArgs } - static JavaGarbageCollector from(JdkVendor vendor, String gcName) { - if (vendor.equals(JdkVendor.IBM)) { - IBM_ALL - } else if (gcName.equals("PS MarkSweep")) { - ORACLE_PARALLEL_CMS - } else if (gcName.equals("MarkSweepCompact")) { - ORACLE_SERIAL9 - } else if (gcName.equals("G1 Old Generation")) { - ORACLE_G1 - } else { - UNKNOWN - } - } - - public String getJvmArgs() { + String getJvmArgs() { jvmArgs } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java index be581297b5a4d..250c1e23be97a 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/AbstractGradleExecuter.java @@ -76,7 +76,10 @@ import static org.gradle.api.internal.artifacts.BaseRepositoryFactory.PLUGIN_PORTAL_OVERRIDE_URL_PROPERTY; import static org.gradle.integtests.fixtures.RepoScriptBlockUtil.gradlePluginRepositoryMirrorUrl; -import static org.gradle.integtests.fixtures.executer.AbstractGradleExecuter.CliDaemonArgument.*; +import static org.gradle.integtests.fixtures.executer.AbstractGradleExecuter.CliDaemonArgument.DAEMON; +import static org.gradle.integtests.fixtures.executer.AbstractGradleExecuter.CliDaemonArgument.FOREGROUND; +import static org.gradle.integtests.fixtures.executer.AbstractGradleExecuter.CliDaemonArgument.NOT_DEFINED; +import static org.gradle.integtests.fixtures.executer.AbstractGradleExecuter.CliDaemonArgument.NO_DAEMON; import static org.gradle.integtests.fixtures.executer.OutputScrapingExecutionResult.STACK_TRACE_ELEMENT; import static org.gradle.internal.service.scopes.DefaultGradleUserHomeScopeServiceRegistry.REUSE_USER_HOME_SERVICES; import static org.gradle.util.CollectionUtils.collect; @@ -519,8 +522,10 @@ protected List getImplicitBuildJvmArgs() { } if (isSharedDaemons()) { + buildJvmOpts.add("-Xms256m"); buildJvmOpts.add("-Xmx1024m"); } else { + buildJvmOpts.add("-Xms256m"); buildJvmOpts.add("-Xmx512m"); } if (JVM_VERSION_DETECTOR.getJavaVersion(Jvm.forHome(getJavaHome())).compareTo(JavaVersion.VERSION_1_8) < 0) { @@ -1376,7 +1381,6 @@ protected DurationMeasurement getDurationMeasurement() { private static LoggingServiceRegistry newCommandLineProcessLogging() { LoggingServiceRegistry loggingServices = LoggingServiceRegistry.newEmbeddableLogging(); LoggingManagerInternal rootLoggingManager = loggingServices.get(DefaultLoggingManagerFactory.class).getRoot(); -// rootLoggingManager.captureSystemSources(); rootLoggingManager.attachSystemOutAndErr(); return loggingServices; } @@ -1401,7 +1405,7 @@ protected GradleExecuter configureConsoleCommandLineArgs() { } private boolean errorsShouldAppearOnStdout() { - // If stderr is attached to the console or if we'll use the fallback console - return (consoleAttachment.isStderrAttached() && consoleAttachment.isStdoutAttached()) || (consoleAttachment == ConsoleAttachment.NOT_ATTACHED && (consoleType == ConsoleOutput.Rich || consoleType == ConsoleOutput.Verbose)); + // If stdout and stderr are attached to the console + return consoleAttachment.isStderrAttached() && consoleAttachment.isStdoutAttached(); } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ConsoleAttachment.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ConsoleAttachment.java index fd1525b89bcec..31bacb8d63efe 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ConsoleAttachment.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ConsoleAttachment.java @@ -21,7 +21,6 @@ public enum ConsoleAttachment { NOT_ATTACHED("not attached to a console", null), ATTACHED("console attached to both stdout and stderr", TestConsoleMetadata.BOTH), - ATTACHED_NEITHER("console detected but not attached to either stdout or stderr", TestConsoleMetadata.NEITHER), ATTACHED_STDOUT_ONLY("console attached to stdout only", TestConsoleMetadata.STDOUT_ONLY), ATTACHED_STDERR_ONLY("console attached to stderr only", TestConsoleMetadata.STDERR_ONLY); diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java index 4a3080ea2bd51..0cb01b3ffcbb6 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/DaemonGradleExecuter.java @@ -23,7 +23,6 @@ import java.util.List; import static java.util.Arrays.asList; -import static org.apache.commons.collections.CollectionUtils.containsAny; public class DaemonGradleExecuter extends NoDaemonGradleExecuter { @@ -60,7 +59,7 @@ protected boolean isDaemonExplicitlyRequired() { protected List getAllArgs() { List args = new ArrayList(super.getAllArgs()); if(!isQuiet() && isAllowExtraLogging()) { - if (!containsAny(args, asList("-i", "--info", "-d", "--debug", "-w", "--warn", "-q", "--quiet"))) { + if (!containsLoggingArgument(args)) { args.add(0, "-i"); } } @@ -73,6 +72,15 @@ protected List getAllArgs() { return args; } + private boolean containsLoggingArgument(List args) { + for (String logArg : asList("-i", "--info", "-d", "--debug", "-w", "--warn", "-q", "--quiet")) { + if (args.contains(logArg)) { + return true; + } + } + return false; + } + @Override protected void transformInvocation(GradleInvocation invocation) { super.transformInvocation(invocation); diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ErrorsOnStdoutScrapingExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ErrorsOnStdoutScrapingExecutionResult.java index d47456d83f78a..47880ccbfd3f7 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ErrorsOnStdoutScrapingExecutionResult.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ErrorsOnStdoutScrapingExecutionResult.java @@ -38,6 +38,16 @@ public String getNormalizedOutput() { return delegate.getNormalizedOutput(); } + @Override + public String getFormattedOutput() { + return delegate.getFormattedOutput(); + } + + @Override + public String getPlainTextOutput() { + return delegate.getPlainTextOutput(); + } + @Override public GroupedOutputFixture getGroupedOutput() { return delegate.getGroupedOutput(); @@ -59,18 +69,6 @@ public boolean hasErrorOutput(String expectedOutput) { return getOutput().contains(expectedOutput); } - @Override - public ExecutionResult assertHasRawErrorOutput(String expectedOutput) { - delegate.assertRawOutputContains(expectedOutput); - return this; - } - - @Override - public ExecutionResult assertRawOutputContains(String expectedOutput) { - delegate.assertRawOutputContains(expectedOutput); - return this; - } - @Override public ExecutionResult assertOutputEquals(String expectedOutput, boolean ignoreExtraLines, boolean ignoreLineOrder) { delegate.assertOutputEquals(expectedOutput, ignoreExtraLines, ignoreLineOrder); diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java index 6bda1a9fb55e9..bccf67ca12112 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/ExecutionResult.java @@ -42,6 +42,16 @@ public interface ExecutionResult { */ String getNormalizedOutput(); + /** + * Stdout of the Gradle execution, with ANSI characters interpreted and text attributes rendered as plain text. + */ + String getFormattedOutput(); + + /** + * Stdout of the Gradle execution, with ANSI characters interpreted and text attributes discarded. + */ + String getPlainTextOutput(); + /** * Returns a fixture that parses the output and forms them into the expected groups */ @@ -61,13 +71,6 @@ public interface ExecutionResult { */ ExecutionResult assertHasErrorOutput(String expectedOutput); - /** - * Asserts that this result includes the given error log message in the raw output (including ANSI characters and build result message). - * - * @param expectedOutput The expected log message, with line endings normalized to a newline character. - */ - ExecutionResult assertHasRawErrorOutput(String expectedOutput); - /** * Returns true when this result includes the given error log message. Does not consider any text in or following the build result message (use {@link #assertHasPostBuildOutput(String)} instead). * @@ -84,13 +87,6 @@ public interface ExecutionResult { */ ExecutionResult assertOutputContains(String expectedOutput); - /** - * Asserts that this result includes the given non-error log message (including ANSI characters and build result message). - * - * @param expectedOutput The expected log message, with line endings normalized to a newline character. - */ - ExecutionResult assertRawOutputContains(String expectedOutput); - /** * Asserts that the given content includes the given log message. * diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java index e5bb1eff18471..f36cbbf3341c9 100755 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/InProcessGradleExecuter.java @@ -77,7 +77,6 @@ import org.gradle.util.DeprecationLogger; import org.gradle.util.GUtil; import org.gradle.util.GradleVersion; -import org.gradle.util.SetSystemProperties; import org.hamcrest.Matcher; import java.io.ByteArrayOutputStream; @@ -295,7 +294,6 @@ private BuildResult doRun(OutputStream outputStream, OutputStream errorStream, B } finally { // Restore the environment System.setProperties(originalSysProperties); - resetTempDirLocation(); processEnvironment.maybeSetProcessDir(originalUserDir); for (String envVar : changedEnvVars) { String oldValue = originalEnv.get(envVar); @@ -310,20 +308,11 @@ private BuildResult doRun(OutputStream outputStream, OutputStream errorStream, B } } - private void resetTempDirLocation() { - SetSystemProperties.resetTempDirLocation(); - } - private LoggingManagerInternal createLoggingManager(StartParameter startParameter, OutputStream outputStream, OutputStream errorStream) { LoggingManagerInternal loggingManager = GLOBAL_SERVICES.getFactory(LoggingManagerInternal.class).create(); loggingManager.captureSystemSources(); ConsoleOutput consoleOutput = startParameter.getConsoleOutput(); - if (consoleOutput == ConsoleOutput.Auto) { - // IDEA runs tests attached to a console, use plain so test can assume never attached to a console - // Should really run all tests against a plain and a rich console to make these assumptions explicit - consoleOutput = ConsoleOutput.Plain; - } loggingManager.attachConsole(new TeeOutputStream(System.out, outputStream), new TeeOutputStream(System.err, errorStream), consoleOutput, consoleAttachment.getConsoleMetaData()); return loggingManager; @@ -338,7 +327,6 @@ private BuildResult executeBuild(GradleInvocation invocation, OutputStream outpu } Map implicitJvmSystemProperties = getImplicitJvmSystemProperties(); System.getProperties().putAll(implicitJvmSystemProperties); - resetTempDirLocation(); // TODO: Fix tests that rely on this being set before we process arguments like this... StartParameterInternal startParameter = new StartParameterInternal(); @@ -512,6 +500,16 @@ public String getNormalizedOutput() { return outputResult.getNormalizedOutput(); } + @Override + public String getFormattedOutput() { + return outputResult.getFormattedOutput(); + } + + @Override + public String getPlainTextOutput() { + return outputResult.getPlainTextOutput(); + } + @Override public GroupedOutputFixture getGroupedOutput() { return outputResult.getGroupedOutput(); @@ -557,18 +555,6 @@ public ExecutionResult assertHasErrorOutput(String expectedOutput) { return this; } - @Override - public ExecutionResult assertHasRawErrorOutput(String expectedOutput) { - outputResult.assertHasRawErrorOutput(expectedOutput); - return this; - } - - @Override - public ExecutionResult assertRawOutputContains(String expectedOutput) { - outputResult.assertRawOutputContains(expectedOutput); - return this; - } - public String getError() { return outputResult.getError(); } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/LogContent.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/LogContent.java index 41c2d3a0ab368..227ef5fe72e3d 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/LogContent.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/LogContent.java @@ -18,48 +18,46 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; -import org.apache.commons.lang.StringUtils; -import org.fusesource.jansi.AnsiOutputStream; +import net.rubygrapefruit.ansi.AnsiParser; +import net.rubygrapefruit.ansi.console.AnsiConsole; +import net.rubygrapefruit.ansi.console.DiagnosticConsole; +import net.rubygrapefruit.ansi.token.NewLine; +import net.rubygrapefruit.ansi.token.Text; import org.gradle.api.Action; import org.gradle.api.UncheckedIOException; import org.gradle.internal.Pair; import javax.annotation.Nullable; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; -import java.util.stream.Collectors; public class LogContent { private final static Pattern DEBUG_PREFIX = Pattern.compile("\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[\\w+] \\[.+?] "); - private final static String PROGRESS_BAR_PATTERN = "<[-=(\u001b\\[\\d+[a-zA-Z;])]*> \\d+% (INITIALIZ|CONFIGUR|EXECUT|WAIT)ING( \\[((\\d+h )? \\d+m )?\\d+s\\])?"; - private final static String WORK_IN_PROGRESS_PATTERN = "\u001b\\[\\d+[a-zA-Z]> (IDLE|[:a-z][\\w\\s\\d:>/\\\\\\.]+)\u001b\\[\\d*[a-zA-Z]"; - private final static String DOWN_MOVEMENT_WITH_NEW_LINE_PATTERN = "\u001b\\[\\d+B\\n"; - private final static Pattern WORK_IN_PROGRESS_AREA_PATTERN = Pattern.compile(PROGRESS_BAR_PATTERN + "|" + WORK_IN_PROGRESS_PATTERN + "|" + DOWN_MOVEMENT_WITH_NEW_LINE_PATTERN); private final static Pattern JAVA_ILLEGAL_ACCESS_WARNING_PATTERN = Pattern.compile("(?ms)WARNING: An illegal reflective access operation has occurred$.+?" + "^WARNING: All illegal access operations will be denied in a future release\r?\n"); private final ImmutableList lines; private final boolean definitelyNoDebugPrefix; + private final boolean definitelyNoAnsiChars; private final LogContent rawContent; - private LogContent(ImmutableList lines, boolean definitelyNoDebugPrefix, LogContent rawContent) { + private LogContent(ImmutableList lines, boolean definitelyNoDebugPrefix, boolean definitelyNoAnsiChars, LogContent rawContent) { this.lines = lines; this.rawContent = rawContent == null ? this : rawContent; this.definitelyNoDebugPrefix = definitelyNoDebugPrefix || lines.isEmpty(); + this.definitelyNoAnsiChars = definitelyNoAnsiChars || lines.isEmpty(); } /** * Creates a new instance, from raw characters. */ public static LogContent of(String chars) { - String stripped = stripWorkInProgressArea(chars); - LogContent raw = new LogContent(toLines(stripped), false, null); - return new LogContent(toLines(stripJavaIllegalAccessWarnings(stripped)), false, raw); + LogContent raw = new LogContent(toLines(chars), false, false, null); + return new LogContent(toLines(stripJavaIllegalAccessWarnings(chars)), false, false, raw); } private static ImmutableList toLines(String chars) { @@ -91,18 +89,11 @@ private static ImmutableList toLines(String chars) { * Creates a new instance from a sequence of lines (without the line separators). */ public static LogContent of(List lines) { - return new LogContent(ImmutableList.copyOf(lines), false, null); + return new LogContent(ImmutableList.copyOf(lines), false, false, null); } public static LogContent empty() { - return new LogContent(ImmutableList.of(), true, null); - } - - /** - * Returns the original content that this content was built from, after transforms such as {@link #removeDebugPrefix()} or {@link #splitOnFirstMatchingLine(Pattern)}. - */ - public LogContent getRawContent() { - return rawContent; + return new LogContent(ImmutableList.of(), true, true, null); } /** @@ -131,13 +122,6 @@ public ImmutableList getLines() { return lines; } - private LogContent lines(int startLine, int endLine) { - if (rawContent != this) { - throw new UnsupportedOperationException("not implemented"); - } - return new LogContent(lines.subList(startLine, endLine), definitelyNoDebugPrefix, null); - } - /** * Visits each line in this content. The line does not include the line separator. */ @@ -157,8 +141,8 @@ Pair splitOnFirstMatchingLine(Pattern pattern) { for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); if (pattern.matcher(line).matches()) { - LogContent before = new LogContent(lines.subList(0, i), definitelyNoDebugPrefix, rawContent.lines(0, i)); - LogContent after = new LogContent(lines.subList(i, lines.size()), definitelyNoDebugPrefix, rawContent.lines(i, lines.size())); + LogContent before = new LogContent(lines.subList(0, i), definitelyNoDebugPrefix, definitelyNoAnsiChars, rawContent); + LogContent after = new LogContent(lines.subList(i, lines.size()), definitelyNoDebugPrefix, definitelyNoAnsiChars, rawContent); return Pair.of(before, after); } } @@ -182,7 +166,7 @@ public int countMatches(Pattern pattern) { * Drops the first n lines. */ public LogContent drop(int i) { - return new LogContent(lines.subList(i, lines.size()), definitelyNoDebugPrefix, rawContent.lines(i, lines.size())); + return new LogContent(lines.subList(i, lines.size()), definitelyNoDebugPrefix, definitelyNoAnsiChars, rawContent); } /** @@ -201,53 +185,70 @@ public LogContent removeDebugPrefix() { result.add(line); } } - return new LogContent(ImmutableList.copyOf(result), true, rawContent); + return new LogContent(ImmutableList.copyOf(result), true, definitelyNoAnsiChars, rawContent); } /** - * Returns a copy of this log content with ANSI control characters removed. + * Returns a copy of this log content with ANSI control characters interpreted to produce plain text. */ - public LogContent removeAnsiChars() { - if (lines.isEmpty()) { + public LogContent ansiCharsToPlainText() { + if (definitelyNoAnsiChars) { return this; } try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Writer writer = new OutputStreamWriter(new AnsiOutputStream(baos)); - for (int i = 0; i < lines.size(); i++) { - if (i > 0) { - writer.write("\n"); + AnsiConsole console = interpretAnsiChars(); + StringBuilder result = new StringBuilder(); + console.contents(token -> { + if (token instanceof Text) { + result.append(((Text) token).getText()); + } else if (token instanceof NewLine) { + result.append("\n"); } - writer.write(lines.get(i)); - } - writer.flush(); - return new LogContent(toLines(baos.toString()), definitelyNoDebugPrefix, rawContent); + }); + return new LogContent(toLines(result.toString()), definitelyNoDebugPrefix, true, rawContent); } catch (IOException e) { throw new UncheckedIOException(e); } } /** - * Remove all blank lines. + * Returns a copy of this log content with ANSI control characters interpreted to produce plain text with text attributes included. */ - public LogContent removeBlankLines() { - List nonBlankLines = lines.stream().filter(StringUtils::isNotBlank).collect(Collectors.toList()); - return new LogContent(ImmutableList.copyOf(nonBlankLines), definitelyNoDebugPrefix, rawContent); + public LogContent ansiCharsToColorText() { + if (definitelyNoAnsiChars) { + return this; + } + try { + AnsiConsole console = interpretAnsiChars(); + DiagnosticConsole diagnosticConsole = new DiagnosticConsole(); + for (int i = 0; i < console.getRows().size(); i++) { + AnsiConsole.Row row = console.getRows().get(i); + if (i > 0) { + diagnosticConsole.visit(NewLine.INSTANCE); + } + row.visit(diagnosticConsole); + } + return new LogContent(toLines(diagnosticConsole.toString()), definitelyNoDebugPrefix, true, rawContent); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } - public static String stripWorkInProgressArea(String output) { - String result = output; - for (int i = 1; i <= 10; ++i) { - result = result.replaceAll(workInProgressAreaScrollingPattern(i), ""); + private AnsiConsole interpretAnsiChars() throws IOException { + AnsiConsole console = new AnsiConsole(); + AnsiParser parser = new AnsiParser(); + Writer writer = new OutputStreamWriter(parser.newParser("utf-8", console)); + for (int i = 0; i < lines.size(); i++) { + if (i > 0) { + writer.write("\n"); + } + writer.write(lines.get(i)); } - return WORK_IN_PROGRESS_AREA_PATTERN.matcher(result).replaceAll(""); + writer.flush(); + return console; } public static String stripJavaIllegalAccessWarnings(String result) { return JAVA_ILLEGAL_ACCESS_WARNING_PATTERN.matcher(result).replaceAll(""); } - - private static String workInProgressAreaScrollingPattern(int scroll) { - return "(\u001b\\[0K\\n){" + scroll + "}\u001b\\[" + scroll + "A"; - } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java index d285303ea575c..f2a47c2b89758 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailure.java @@ -63,13 +63,13 @@ public static OutputScrapingExecutionFailure from(String output, String error) { protected OutputScrapingExecutionFailure(String output, String error) { super(LogContent.of(output), LogContent.of(error)); - LogContent withoutDebug = LogContent.of(output).removeAnsiChars().removeDebugPrefix(); + LogContent withoutDebug = LogContent.of(output).ansiCharsToPlainText().removeDebugPrefix(); // Find failure section Pair match = withoutDebug.splitOnFirstMatchingLine(FAILURE_PATTERN); if (match == null) { // Not present in output, check error output. - match = LogContent.of(error).removeAnsiChars().removeDebugPrefix().splitOnFirstMatchingLine(FAILURE_PATTERN); + match = LogContent.of(error).ansiCharsToPlainText().removeDebugPrefix().splitOnFirstMatchingLine(FAILURE_PATTERN); if (match != null) { match = Pair.of(withoutDebug, match.getRight()); } else { diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java index d1d463958834e..94d62a2b1858f 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionResult.java @@ -23,7 +23,7 @@ import org.gradle.internal.featurelifecycle.LoggingDeprecatedFeatureHandler; import org.gradle.launcher.daemon.client.DaemonStartupMessage; import org.gradle.launcher.daemon.server.DaemonStateCoordinator; -import org.gradle.launcher.daemon.server.health.LowTenuredSpaceDaemonExpirationStrategy; +import org.gradle.launcher.daemon.server.health.LowHeapSpaceDaemonExpirationStrategy; import org.gradle.util.GUtil; import org.junit.ComparisonFailure; @@ -83,7 +83,7 @@ protected OutputScrapingExecutionResult(LogContent output, LogContent error) { this.error = error; // Split out up the output into main content and post build content - LogContent filteredOutput = this.output.removeAnsiChars().removeDebugPrefix(); + LogContent filteredOutput = this.output.ansiCharsToPlainText().removeDebugPrefix(); Pair match = filteredOutput.splitOnFirstMatchingLine(BUILD_RESULT_PATTERN); if (match == null) { this.mainContent = filteredOutput; @@ -92,7 +92,7 @@ protected OutputScrapingExecutionResult(LogContent output, LogContent error) { this.mainContent = match.getLeft(); this.postBuild = match.getRight().drop(1); } - this.errorContent = error.removeAnsiChars(); + this.errorContent = error.ansiCharsToPlainText(); } public String getOutput() { @@ -111,10 +111,20 @@ public String getNormalizedOutput() { return normalize(output); } + @Override + public String getFormattedOutput() { + return output.ansiCharsToColorText().withNormalizedEol(); + } + + @Override + public String getPlainTextOutput() { + return output.ansiCharsToPlainText().withNormalizedEol(); + } + @Override public GroupedOutputFixture getGroupedOutput() { if (groupedOutputFixture == null) { - groupedOutputFixture = new GroupedOutputFixture(getMainContent().getRawContent().withNormalizedEol()); + groupedOutputFixture = new GroupedOutputFixture(getMainContent()); } return groupedOutputFixture; } @@ -131,7 +141,7 @@ private String normalize(LogContent output) { } else if (line.contains(DaemonStateCoordinator.DAEMON_WILL_STOP_MESSAGE)) { // Remove the "Daemon will be shut down" message i++; - } else if (line.contains(LowTenuredSpaceDaemonExpirationStrategy.EXPIRE_DAEMON_MESSAGE)) { + } else if (line.contains(LowHeapSpaceDaemonExpirationStrategy.EXPIRE_DAEMON_MESSAGE)) { // Remove the "Expiring Daemon" message i++; } else if (line.contains(LoggingDeprecatedFeatureHandler.WARNING_SUMMARY)) { @@ -185,7 +195,7 @@ public ExecutionResult assertOutputContains(String expectedOutput) { @Override public boolean hasErrorOutput(String expectedOutput) { - return getError().contains(expectedOutput) || getRawError().contains(expectedOutput); + return getError().contains(expectedOutput); } @Override @@ -193,24 +203,10 @@ public ExecutionResult assertHasErrorOutput(String expectedOutput) { return assertContentContains(errorContent.withNormalizedEol(), expectedOutput, "Error output"); } - @Override - public ExecutionResult assertHasRawErrorOutput(String expectedOutput) { - return assertContentContains(getError(), expectedOutput, "Error output"); - } - - @Override - public ExecutionResult assertRawOutputContains(String expectedOutput) { - return assertContentContains(getOutput(), expectedOutput, "Build output"); - } - public String getError() { return error.withNormalizedEol(); } - public String getRawError() { - return errorContent.getRawContent().withNormalizedEol(); - } - public List getExecutedTasks() { return ImmutableList.copyOf(findExecutedTasksInOrderStarted()); } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/ArtifactResolutionOmittingOutputNormalizer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/ArtifactResolutionOmittingOutputNormalizer.groovy index 3cdbfdf9c2f63..567767f06464e 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/ArtifactResolutionOmittingOutputNormalizer.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/ArtifactResolutionOmittingOutputNormalizer.groovy @@ -15,13 +15,14 @@ */ package org.gradle.integtests.fixtures.logging +import groovy.transform.CompileStatic import org.gradle.internal.jvm.Jvm -import org.gradle.samples.test.normalizer.OutputNormalizer import org.gradle.samples.executor.ExecutionMetadata +import org.gradle.samples.test.normalizer.OutputNormalizer import javax.annotation.Nullable - +@CompileStatic class ArtifactResolutionOmittingOutputNormalizer implements OutputNormalizer { @Override String normalize(String commandOutput, @Nullable ExecutionMetadata executionMetadata) { diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/DependencyInsightOutputNormalizer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/DependencyInsightOutputNormalizer.groovy new file mode 100644 index 0000000000000..12ee10e424595 --- /dev/null +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/DependencyInsightOutputNormalizer.groovy @@ -0,0 +1,30 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.fixtures.logging + +import groovy.transform.CompileStatic +import org.gradle.samples.executor.ExecutionMetadata +import org.gradle.samples.test.normalizer.OutputNormalizer + +@CompileStatic +class DependencyInsightOutputNormalizer implements OutputNormalizer { + + @Override + String normalize(String output, ExecutionMetadata executionMetadata) { + output.replaceAll("org\\.gradle\\.jvm\\.version[ ]+= [0-9]+", "org.gradle.jvm.version = 11") + } +} diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/GroupedOutputFixture.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/GroupedOutputFixture.java index 0ec5e1eb2765f..790e00acd3d2f 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/GroupedOutputFixture.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/GroupedOutputFixture.java @@ -32,10 +32,10 @@ */ public class GroupedOutputFixture { /** - * All tasks will start with > Task, captures everything starting with : and going until a control char + * All tasks will start with > Task, captures everything starting with : and going until end of line */ - private final static String TASK_HEADER = "> Task (:[\\w:]*) ?(FAILED|FROM-CACHE|UP-TO-DATE|SKIPPED|NO-SOURCE)?\\n?"; - private final static String TRANSFORMATION_HEADER = "> Transform (artifact|file) ([^\\n]+) with ([^\\n]+)\\n?"; + private final static String TASK_HEADER = "> Task (:[\\w:]*) ?(FAILED|FROM-CACHE|UP-TO-DATE|SKIPPED|NO-SOURCE)?\\n"; + private final static String TRANSFORMATION_HEADER = "> Transform (artifact|file) ([^\\n]+) with ([^\\n]+)\\n"; private final static String EMBEDDED_BUILD_START = "> :\\w* > [:\\w]+"; private final static String BUILD_STATUS_FOOTER = "BUILD SUCCESSFUL"; @@ -65,21 +65,21 @@ private static Pattern patternForHeader(String header) { return Pattern.compile(pattern); } - private final String originalOutput; + private final LogContent originalOutput; private final String strippedOutput; private Map tasks; private Map transformations; - public GroupedOutputFixture(String output) { + public GroupedOutputFixture(LogContent output) { this.originalOutput = output; this.strippedOutput = parse(output); } - private String parse(String output) { + private String parse(LogContent output) { tasks = new HashMap(); transformations = new HashMap(); - String strippedOutput = LogContent.of(output).removeAnsiChars().withNormalizedEol(); + String strippedOutput = output.ansiCharsToPlainText().withNormalizedEol(); findOutputs(strippedOutput, TASK_OUTPUT_PATTERN, this::consumeTaskOutput); findOutputs(strippedOutput, TRANSFORMATION_OUTPUT_PATTERN, this::consumeTransformationOutput); @@ -127,7 +127,7 @@ public String getStrippedOutput() { } public String toString() { - return originalOutput; + return originalOutput.withNormalizedEol(); } private void consumeTaskOutput(Matcher matcher) { diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/SampleOutputNormalizer.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/SampleOutputNormalizer.groovy index 3db3d63b723e6..6810703f613a7 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/SampleOutputNormalizer.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/logging/SampleOutputNormalizer.groovy @@ -15,6 +15,7 @@ */ package org.gradle.integtests.fixtures.logging +import groovy.transform.CompileStatic import org.gradle.integtests.fixtures.executer.OutputScrapingExecutionResult import org.gradle.internal.logging.ConsoleRenderer import org.gradle.samples.executor.ExecutionMetadata @@ -22,6 +23,7 @@ import org.gradle.samples.test.normalizer.OutputNormalizer import java.util.regex.Pattern +@CompileStatic class SampleOutputNormalizer implements OutputNormalizer { private static final String NORMALIZED_SAMPLES_PATH = "/home/user/gradle/samples" private static final String NORMALIZED_SAMPLES_FILE_URL = "file:///home/user/gradle/samples/" diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/resolve/ResolveTestFixture.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/resolve/ResolveTestFixture.groovy index f92ec76db0387..a67d80b1f4b0a 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/resolve/ResolveTestFixture.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/integtests/fixtures/resolve/ResolveTestFixture.groovy @@ -532,7 +532,10 @@ allprojects { } String getFileName() { - return "${getName()}${version ? '-' + version : ''}${classifier ? '-' + classifier : ''}${noType ? '' : '.' + getType()}" + if (noType) { + return getName() + } + return "${getName()}${version ? '-' + version : ''}${classifier ? '-' + classifier : ''}.${getType()}" } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/GradleModuleMetadata.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/GradleModuleMetadata.groovy index 38fd7effdd16f..45ebe7e9d6fdc 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/GradleModuleMetadata.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/GradleModuleMetadata.groovy @@ -35,7 +35,7 @@ class GradleModuleMetadata { JsonReader reader = new JsonReader(r) values = readObject(reader) } - assert values.formatVersion == '0.4' + assert values.formatVersion == '1.0' assert values.createdBy.gradle.version == GradleVersion.current().version assert values.createdBy.gradle.buildId variants = (values.variants ?: []).collect { new Variant(it.name, it) } @@ -155,6 +155,10 @@ class GradleModuleMetadata { return ref == null ? null : new ModuleReference(ref.group, ref.module, ref.version, ref.url) } + Map getAttributes() { + values.attributes + } + List getDependencies() { if (dependencies == null) { dependencies = (values.dependencies ?: []).collect { diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/gradle/GradleFileModuleAdapter.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/gradle/GradleFileModuleAdapter.groovy index 96c3fbb7f11cf..c0a2ec0ada627 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/gradle/GradleFileModuleAdapter.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/gradle/GradleFileModuleAdapter.groovy @@ -17,7 +17,7 @@ package org.gradle.test.fixtures.gradle import groovy.json.JsonBuilder -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser import org.gradle.test.fixtures.file.TestFile class GradleFileModuleAdapter { @@ -40,7 +40,7 @@ class GradleFileModuleAdapter { def file = moduleDir.file("$module-${version}.module") def jsonBuilder = new JsonBuilder() jsonBuilder { - formatVersion ModuleMetadataParser.FORMAT_VERSION + formatVersion GradleModuleMetadataParser.FORMAT_VERSION builtBy { gradle { } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/gradle/VariantMetadataSpec.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/gradle/VariantMetadataSpec.groovy index d5bb981db462e..c0925384e9d00 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/gradle/VariantMetadataSpec.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/gradle/VariantMetadataSpec.groovy @@ -64,4 +64,8 @@ class VariantMetadataSpec { void constraint(String group, String module, String version, String reason = null, Map attributes=[:]) { dependencyConstraints << new DependencyConstraintSpec(group, module, version, null, null, null, reason, attributes) } + + void artifact(String name) { + artifacts << new FileSpec(name) + } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyJavaModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyJavaModule.groovy index 2daa8e46da4c9..ba5ca08295c3b 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyJavaModule.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/ivy/IvyJavaModule.groovy @@ -16,7 +16,8 @@ package org.gradle.test.fixtures.ivy - +import org.gradle.api.JavaVersion +import org.gradle.api.attributes.java.TargetJvmVersion import org.gradle.test.fixtures.ModuleArtifact import org.gradle.test.fixtures.PublishedJavaModule import org.gradle.test.fixtures.file.TestFile @@ -29,6 +30,7 @@ class IvyJavaModule extends DelegatingIvyModule implements Publis IvyJavaModule(IvyFileModule backingModule) { super(backingModule) this.backingModule = backingModule + this.backingModule.attributes[TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE.name] = JavaVersion.current().majorVersion } @Override diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenJavaModule.groovy b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenJavaModule.groovy index 80c5652405dfe..e711eac30a15a 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenJavaModule.groovy +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/maven/MavenJavaModule.groovy @@ -16,6 +16,9 @@ package org.gradle.test.fixtures.maven +import org.gradle.api.JavaVersion +import org.gradle.api.attributes.Bundling +import org.gradle.api.attributes.java.TargetJvmVersion import org.gradle.test.fixtures.PublishedJavaModule import org.gradle.util.GUtil @@ -26,6 +29,7 @@ class MavenJavaModule extends DelegatingMavenModule implements MavenJavaModule(MavenFileModule mavenModule) { super(mavenModule) this.mavenModule = mavenModule + this.mavenModule.attributes[TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE.name] = JavaVersion.current().majorVersion } @Override @@ -49,8 +53,17 @@ class MavenJavaModule extends DelegatingMavenModule implements // Verify Gradle metadata particulars assert mavenModule.parsedModuleMetadata.variants*.name as Set == ['apiElements', 'runtimeElements'] as Set - assert mavenModule.parsedModuleMetadata.variant('apiElements').files*.name == [artifact('jar')] - assert mavenModule.parsedModuleMetadata.variant('runtimeElements').files*.name == [artifact('jar')] + def apiElements = mavenModule.parsedModuleMetadata.variant('apiElements') + def runtimeElements = mavenModule.parsedModuleMetadata.variant('runtimeElements') + + assert apiElements.files*.name == [artifact('jar')] + assert runtimeElements.files*.name == [artifact('jar')] + + // Verify it contains some expected attributes + assert apiElements.attributes.containsKey(Bundling.BUNDLING_ATTRIBUTE.name) + assert apiElements.attributes.containsKey(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE.name) + assert runtimeElements.attributes.containsKey(Bundling.BUNDLING_ATTRIBUTE.name) + assert runtimeElements.attributes.containsKey(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE.name) // Verify POM particulars assert mavenModule.parsedPom.packaging == null diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/AbstractFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/AbstractFailure.java new file mode 100644 index 0000000000000..9957266717277 --- /dev/null +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/AbstractFailure.java @@ -0,0 +1,57 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.test.fixtures.server.http; + +import com.sun.net.httpserver.HttpExchange; + +abstract class AbstractFailure implements ResponseProducer, Failure { + private final RuntimeException failure; + + public AbstractFailure(RuntimeException failure) { + this.failure = failure; + } + + @Override + public boolean isFailure() { + return true; + } + + @Override + public RuntimeException getFailure() { + return failure; + } + + @Override + public void writeTo(int requestId, HttpExchange exchange) { + throw new IllegalStateException(); + } + + protected static String withLeadingSlash(String path) { + if (path.startsWith("/")) { + return path; + } else { + return "/" + path; + } + } + + protected static String contextSuffix(String context) { + if (context.isEmpty()) { + return context; + } + return ". " + context; + } +} diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServer.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServer.java index 11090c20e40db..ef15bb97fef4f 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServer.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServer.java @@ -27,6 +27,8 @@ import java.io.File; import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; @@ -54,6 +56,7 @@ public class BlockingHttpServer extends ExternalResource { private final int timeoutMs; private final int serverId; private boolean running; + private int clientVarCounter; public BlockingHttpServer() throws IOException { this(120000); @@ -95,20 +98,42 @@ public URI uri(String resource) { * Returns Java statements to get the given resource. */ public String callFromBuild(String resource) { - URI uri = uri(resource); - return "System.out.println(\"calling " + uri + "\"); try { new java.net.URL(\"" + uri + "\").openConnection().getContentLength(); } catch(Exception e) { throw new RuntimeException(e); }; System.out.println(\"[G] response received\");"; - } - - public String callFromTaskAction(String resource) { - return "getServices().get(" + WorkerLeaseService.class.getCanonicalName() + ".class).withoutProjectLock(new Runnable() { void run() { " + callFromBuild(resource) + " } });"; - } + return callFromBuildUsingExpression("\"" + resource + "\""); + } /** * Returns Java statements to get the given resource, using the given expression to calculate the resource to get. */ public String callFromBuildUsingExpression(String expression) { String uriExpression = "\"" + getUri() + "/\" + " + expression; - return "System.out.println(\"calling \" + " + uriExpression + "); try { new java.net.URL(" + uriExpression + ").openConnection().getContentLength(); } catch(Exception e) { throw new RuntimeException(e); }; System.out.println(\"[G] response received\");"; + int count = clientVarCounter++; + String connectionVar = "connection" + count; + String urlVar = "url" + count; + String streamVar = "inputStream" + count; + StringWriter result = new StringWriter(); + PrintWriter writer = new PrintWriter(result); + writer.print("String " + urlVar + " = " + uriExpression + ";"); + writer.print("System.out.println(\"[G] calling \" + " + urlVar + ");"); + writer.print("try {"); + writer.print(" java.net.URLConnection " + connectionVar + " = new java.net.URL(" + urlVar + ").openConnection();"); + writer.print(" " + connectionVar + ".setReadTimeout(0);"); // to avoid silent retry + writer.print(" " + connectionVar + ".connect();"); + writer.print(" java.io.InputStream " + streamVar + " = " + connectionVar + ".getInputStream();"); + writer.print(" try {"); + writer.print(" while (" + streamVar + ".read() >= 0) {}"); // read entire response + writer.print(" } finally {"); + writer.print(" " + streamVar + ".close();"); + writer.print(" }"); + writer.print("} catch(Exception e) {"); + writer.print(" System.out.println(\"[G] error response received for \" + " + urlVar + ");"); + writer.print(" throw new RuntimeException(\"Received error response from \" + " + urlVar + ", e);"); + writer.print("};"); + writer.println("System.out.println(\"[G] response received for \" + " + urlVar + ");"); + return result.toString(); + } + + public String callFromTaskAction(String resource) { + return "getServices().get(" + WorkerLeaseService.class.getCanonicalName() + ".class).withoutProjectLock(new Runnable() { void run() { " + callFromBuild(resource) + " } });"; } /** diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ChainingHttpHandler.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ChainingHttpHandler.java index e59eff64a7cf9..5ac795e20d9df 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ChainingHttpHandler.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ChainingHttpHandler.java @@ -24,7 +24,7 @@ import org.gradle.internal.time.Clock; import org.gradle.internal.time.Time; -import javax.annotation.Nullable; +import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -111,23 +111,23 @@ public void handle(HttpExchange httpExchange) { System.out.println(String.format("[%d] handling %s", id, outcome.getDisplayName())); try { - ResponseProducer responseProducer = selectProducer(id, httpExchange, outcome); - if (responseProducer != null) { - System.out.println(String.format("[%d] sending response for %s", id, outcome.getDisplayName())); + ResponseProducer responseProducer = selectProducer(id, httpExchange); + System.out.println(String.format("[%d] sending error response for %s", id, outcome.getDisplayName())); + if (!responseProducer.isFailure()) { responseProducer.writeTo(id, httpExchange); } else { - System.out.println(String.format("[%d] sending error response for unexpected request", id)); - if (outcome.method.equals("HEAD")) { - httpExchange.sendResponseHeaders(500, -1); - } else { - byte[] message = String.format("Failed request %s", outcome.getDisplayName()).getBytes(Charsets.UTF_8); - httpExchange.sendResponseHeaders(500, message.length); - httpExchange.getResponseBody().write(message); - } + Throwable failure = responseProducer.getFailure(); + requestFailed(outcome, failure); + sendFailure(httpExchange, 400, outcome); } } catch (Throwable t) { System.out.println(String.format("[%d] handling %s failed with exception", id, outcome.getDisplayName())); - requestFailed(outcome, t); + try { + sendFailure(httpExchange, 500, outcome); + } catch (IOException e) { + // Ignore + } + requestFailed(outcome, new AssertionError(String.format("Failed to handle %s", outcome.getDisplayName()), t)); } finally { requestCompleted(outcome); } @@ -136,6 +136,16 @@ public void handle(HttpExchange httpExchange) { } } + private void sendFailure(HttpExchange httpExchange, int responseCode, RequestOutcome outcome) throws IOException { + if (outcome.method.equals("HEAD")) { + httpExchange.sendResponseHeaders(responseCode, -1); + } else { + byte[] message = String.format("Failed request %s", outcome.getDisplayName()).getBytes(Charsets.UTF_8); + httpExchange.sendResponseHeaders(responseCode, message.length); + httpExchange.getResponseBody().write(message); + } + } + private RequestOutcome requestStarted(HttpExchange httpExchange) { lock.lock(); RequestOutcome outcome; @@ -152,7 +162,7 @@ private void requestFailed(RequestOutcome outcome, Throwable t) { lock.lock(); try { if (outcome.failure == null) { - outcome.failure = new AssertionError(String.format("Failed to handle %s", outcome.getDisplayName()), t); + outcome.failure = t; } } finally { lock.unlock(); @@ -163,18 +173,14 @@ private void requestCompleted(RequestOutcome outcome) { outcome.completed(); } - /** - * Returns null on failure. - */ - @Nullable - private ResponseProducer selectProducer(int id, HttpExchange httpExchange, RequestOutcome outcome) { + private ResponseProducer selectProducer(int id, HttpExchange httpExchange) { lock.lock(); try { requestsStarted++; condition.signalAll(); if (completed) { System.out.println(String.format("[%d] received request %s %s after HTTP server has stopped.", id, httpExchange.getRequestMethod(), httpExchange.getRequestURI())); - return null; + return new UnexpectedRequestFailure(httpExchange.getRequestMethod(), httpExchange.getRequestURI().getPath()); } for (TrackingHttpHandler handler : handlers) { ResponseProducer responseProducer = handler.selectResponseProducer(id, httpExchange); @@ -182,15 +188,10 @@ private ResponseProducer selectProducer(int id, HttpExchange httpExchange, Reque return responseProducer; } } - System.out.println(String.format("[%d] unexpected request %s %s", id, httpExchange.getRequestMethod(), httpExchange.getRequestURI())); - outcome.failure = new AssertionError(String.format("Received unexpected request %s %s", httpExchange.getRequestMethod(), httpExchange.getRequestURI().getPath())); - } catch (Throwable t) { - System.out.println(String.format("[%d] error during handling of request %s %s", id, httpExchange.getRequestMethod(), httpExchange.getRequestURI())); - outcome.failure = new AssertionError(String.format("Failed to handle %s %s", httpExchange.getRequestMethod(), httpExchange.getRequestURI().getPath()), t); + return new UnexpectedRequestFailure(httpExchange.getRequestMethod(), httpExchange.getRequestURI().getPath()); } finally { lock.unlock(); } - return null; } void waitForRequests(int requestCount) { diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierAnyOfRequestHandler.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierAnyOfRequestHandler.java index 1a6d69a3ec1a0..2ba1c7cc5c7e8 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierAnyOfRequestHandler.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierAnyOfRequestHandler.java @@ -17,6 +17,7 @@ package org.gradle.test.fixtures.server.http; import com.sun.net.httpserver.HttpExchange; +import org.gradle.internal.UncheckedException; import org.gradle.internal.time.Clock; import org.gradle.internal.time.Time; @@ -45,7 +46,7 @@ class CyclicBarrierAnyOfRequestHandler implements TrackingHttpHandler, WaitPreco private final WaitPrecondition previous; private long mostRecentEvent; private boolean cancelled; - private AssertionError failure; + private final ExpectationState state = new ExpectationState(); CyclicBarrierAnyOfRequestHandler(Lock lock, int testId, int timeoutMs, int maxConcurrent, WaitPrecondition previous, Collection expectedRequests) { if (expectedRequests.size() < maxConcurrent) { @@ -84,7 +85,7 @@ public void assertCanWait() throws IllegalStateException { } @Override - public ResponseProducer selectResponseProducer(int id, HttpExchange exchange) throws Exception { + public ResponseProducer selectResponseProducer(int id, HttpExchange exchange) { ResourceHandlerWrapper handler; lock.lock(); try { @@ -101,9 +102,9 @@ public ResponseProducer selectResponseProducer(int id, HttpExchange exchange) th String path = exchange.getRequestURI().getPath().substring(1); handler = selectPending(notReceived, path); if (handler == null || !handler.getMethod().equals(exchange.getRequestMethod()) || waitingFor == 0) { - failure = new UnexpectedRequestException(String.format("Unexpected request %s /%s received. Waiting for %s further requests, already received %s, released %s, still expecting %s.", exchange.getRequestMethod(), path, waitingFor, received, released, format(notReceived))); + ResponseProducer failure = state.unexpectedRequest(exchange.getRequestMethod(), path, describeCurrentState()); condition.signalAll(); - throw failure; + return failure; } notReceived.remove(handler); @@ -115,25 +116,41 @@ public ResponseProducer selectResponseProducer(int id, HttpExchange exchange) th condition.signalAll(); } - while (!handler.isReleased() && failure == null && !cancelled) { + if (state.isFailed()) { + // Broken in another thread + System.out.println(String.format("[%d] failure in another thread", id)); + return state.alreadyFailed(exchange.getRequestMethod(), path, describeCurrentState()); + } + + while (!handler.isReleased() && !state.isFailed() && !cancelled) { long waitMs = mostRecentEvent + timeoutMs - clock.getCurrentTime(); if (waitMs < 0) { + ResponseProducer failure; if (waitingFor > 0) { System.out.println(String.format("[%d] timeout waiting for other requests", id)); - throw timeoutWaitingForRequests(); + failure = state.timeout(exchange.getRequestMethod(), path, "waiting for other requests", describeCurrentState()); + } else { + System.out.println(String.format("[%d] timeout waiting to be released", id)); + failure = state.timeout(exchange.getRequestMethod(), path, "waiting to be released", describeCurrentState()); } - System.out.println(String.format("[%d] timeout waiting to be released", id)); - failure = new AssertionError(String.format("Timeout waiting to be released. Waiting for %s further requests, received %s, released %s, not yet received %s.", waitingFor, received, released, format(notReceived))); condition.signalAll(); - throw failure; + return failure; } System.out.println(String.format("[%d] waiting to be released. Still waiting for %s further requests, already received %s", id, waitingFor, received)); - condition.await(waitMs, TimeUnit.MILLISECONDS); + try { + condition.await(waitMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + throw UncheckedException.throwAsUncheckedException(e); + } } - if (failure != null) { + if (state.isFailed()) { // Broken in another thread System.out.println(String.format("[%d] failure in another thread", id)); - throw failure; + if (waitingFor > 0) { + return state.failureWhileWaiting(exchange.getRequestMethod(), path, "waiting for other requests", describeCurrentState()); + } else { + return state.failureWhileWaiting(exchange.getRequestMethod(), path, "waiting to be released", describeCurrentState()); + } } if (cancelled) { return new ResponseProducer() { @@ -154,6 +171,10 @@ public void writeTo(int requestId, HttpExchange exchange) { return handler; } + private String describeCurrentState() { + return String.format("Waiting for %s further requests, received %s, released %s, not yet received %s", waitingFor, received, released, format(notReceived)); + } + @Override public void cancelBlockedRequests() { lock.lock(); @@ -169,12 +190,12 @@ public void cancelBlockedRequests() { public void assertComplete(Collection failures) throws AssertionError { lock.lock(); try { - if (failure != null) { + if (state.isFailed()) { // Already reported return; } if (!notReceived.isEmpty()) { - failures.add(new AssertionError(String.format("Did not handle all expected requests. Waiting for %d further requests, received %s, released %s, not yet received %s.", waitingFor, received, released, format(notReceived)))); + failures.add(new AssertionError(String.format("Did not handle all expected requests. %s", describeCurrentState()))); } } finally { lock.unlock(); @@ -269,11 +290,12 @@ public void waitForAllPendingCalls() { mostRecentEvent = now; } - while (waitingFor > 0 && failure == null) { + while (waitingFor > 0 && !state.isFailed()) { long waitMs = mostRecentEvent + timeoutMs - clock.getCurrentTime(); if (waitMs < 0) { System.out.println(String.format("[%d] timeout waiting for expected requests.", testId)); - throw timeoutWaitingForRequests(); + timeoutWaitingForRequests(); + break; } System.out.println(String.format("[%d] waiting for %d further requests, received %s, released %s, not yet received %s", testId, waitingFor, received, released, format(notReceived))); try { @@ -282,8 +304,8 @@ public void waitForAllPendingCalls() { throw new RuntimeException(e); } } - if (failure != null) { - throw failure; + if (state.isFailed()) { + throw state.getWaitFailure(describeCurrentState()); } System.out.println(String.format("[%d] expected requests received, received %s, released %s, not yet received %s", testId, received, released, format(notReceived))); } finally { @@ -291,9 +313,8 @@ public void waitForAllPendingCalls() { } } - private AssertionError timeoutWaitingForRequests() { - failure = new AssertionError(String.format("Timeout waiting for expected requests. Waiting for %d further requests, received %s, released %s, not yet received %s.", waitingFor, received, released, format(notReceived))); + private void timeoutWaitingForRequests() { + state.timeout("waiting for expected requests", describeCurrentState()); condition.signalAll(); - throw failure; } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierRequestHandler.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierRequestHandler.java index 0de988dd9563c..242ff1379a906 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierRequestHandler.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/CyclicBarrierRequestHandler.java @@ -17,6 +17,7 @@ package org.gradle.test.fixtures.server.http; import com.sun.net.httpserver.HttpExchange; +import org.gradle.internal.UncheckedException; import org.gradle.internal.time.Time; import org.gradle.internal.time.Timer; @@ -39,7 +40,7 @@ class CyclicBarrierRequestHandler implements TrackingHttpHandler, WaitPreconditi private final WaitPrecondition previous; private long mostRecentEvent; private boolean cancelled; - private AssertionError failure; + private final ExpectationState state = new ExpectationState(); CyclicBarrierRequestHandler(Lock lock, int timeoutMs, WaitPrecondition previous, Collection expectations) { this.lock = lock; @@ -74,7 +75,7 @@ public void assertCanWait() throws AssertionError { } @Override - public ResponseProducer selectResponseProducer(int id, HttpExchange httpExchange) throws Exception { + public ResponseProducer selectResponseProducer(int id, HttpExchange httpExchange) { ResourceHandler handler; lock.lock(); try { @@ -91,9 +92,9 @@ public ResponseProducer selectResponseProducer(int id, HttpExchange httpExchange String path = httpExchange.getRequestURI().getPath().substring(1); handler = selectPending(pending, path); if (handler == null || !handler.getMethod().equals(httpExchange.getRequestMethod())) { - failure = new UnexpectedRequestException(String.format("Unexpected request %s /%s received. Waiting for %s, already received %s.", httpExchange.getRequestMethod(), path, format(pending), received)); + ResponseProducer failure = state.unexpectedRequest(httpExchange.getRequestMethod(), path, describeCurrentState()); condition.signalAll(); - throw failure; + return failure; } received.add(httpExchange.getRequestMethod() + " /" + path); @@ -102,23 +103,34 @@ public ResponseProducer selectResponseProducer(int id, HttpExchange httpExchange condition.signalAll(); } - while (!pending.isEmpty() && failure == null && !cancelled) { + if (state.isFailed()) { + // Failed in another thread + System.out.println(String.format("[%d] failure in another thread", id)); + return state.alreadyFailed(httpExchange.getRequestMethod(), path, describeCurrentState()); + } + + while (!pending.isEmpty() && !state.isFailed() && !cancelled) { long waitMs = mostRecentEvent + timeoutMs - timer.getElapsedMillis(); if (waitMs < 0) { System.out.println(String.format("[%d] timeout waiting for other requests", id)); - failure = new AssertionError(String.format("Timeout waiting for expected requests to be received. Still waiting for %s, received %s.", format(pending), received)); + ResponseProducer failure = state.timeout(httpExchange.getRequestMethod(), path, "waiting for other requests", describeCurrentState()); condition.signalAll(); - throw failure; + return failure; } System.out.println(String.format("[%d] waiting for other requests. Still waiting for %s", id, format(pending))); - condition.await(waitMs, TimeUnit.MILLISECONDS); + try { + condition.await(waitMs, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + UncheckedException.throwAsUncheckedException(e); + } } - if (failure != null) { + if (state.isFailed()) { // Failed in another thread System.out.println(String.format("[%d] failure in another thread", id)); - throw failure; + return state.failureWhileWaiting(httpExchange.getRequestMethod(), path, "waiting for other requests", describeCurrentState()); } + if (cancelled) { return new ResponseProducer() { @Override @@ -139,6 +151,10 @@ public void writeTo(int requestId, HttpExchange exchange) { return handler; } + private String describeCurrentState() { + return String.format("Waiting for %s, already received %s", format(pending), received); + } + @Override public void cancelBlockedRequests() { lock.lock(); @@ -179,12 +195,12 @@ static T selectPending(List handlers, String path public void assertComplete(Collection failures) throws AssertionError { lock.lock(); try { - if (failure != null) { + if (state.isFailed()) { // Already reported return; } if (!pending.isEmpty()) { - failures.add(new AssertionError(String.format("Did not receive expected requests. Waiting for %s, received %s", format(pending), received))); + failures.add(new AssertionError(String.format("Did not receive expected requests. %s", describeCurrentState()))); } } finally { lock.unlock(); diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ExpectationState.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ExpectationState.java new file mode 100644 index 0000000000000..99e45d9837edc --- /dev/null +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ExpectationState.java @@ -0,0 +1,112 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.test.fixtures.server.http; + +import static org.gradle.test.fixtures.server.http.AbstractFailure.contextSuffix; +import static org.gradle.test.fixtures.server.http.AbstractFailure.withLeadingSlash; + +class ExpectationState { + + private enum FailureType { + None, UnexpectedRequest, Timeout + } + + private FailureType failure = FailureType.None; + private String unexpectedMethod; + private String unexpectedPath; + + public boolean isFailed() { + return failure != FailureType.None; + } + + /** + * Signals that an unexpected request was received. + * + * @return A response to return to the client + */ + public ResponseProducer unexpectedRequest(String requestMethod, String path, String context) { + if (failure == FailureType.None) { + failure = FailureType.UnexpectedRequest; + unexpectedMethod = requestMethod; + unexpectedPath = path; + } + return new UnexpectedRequestFailure(requestMethod, path, context); + } + + /** + * Signals that a timeout occurred waiting to handle the given request. + * + * @return A response to return to the client + */ + public ResponseProducer timeout(String requestMethod, String path, String waitingFor, String context) { + if (failure == FailureType.None) { + failure = FailureType.Timeout; + } + return new RequestConditionFailure(requestMethod, path, String.format("Failed to handle %s %s due to a timeout %s%s", requestMethod, withLeadingSlash(path), waitingFor, contextSuffix(context))); + } + + /** + * Signals that a timeout occurred waiting for test condition to become true. + */ + public void timeout(String waitingFor, String context) { + if (failure == FailureType.None) { + failure = FailureType.Timeout; + } + } + + /** + * Creates a response to return to the client for an expected request received after a failure. + */ + public ResponseProducer alreadyFailed(String requestMethod, String path, String context) { + switch (failure) { + case UnexpectedRequest: + return new RequestConditionFailure(requestMethod, path, String.format("Failed to handle %s %s due to unexpected request %s %s%s", requestMethod, withLeadingSlash(path), unexpectedMethod, withLeadingSlash(unexpectedPath), contextSuffix(context))); + case Timeout: + return new RequestConditionFailure(requestMethod, path, String.format("Failed to handle %s %s due to a previous timeout%s", requestMethod, withLeadingSlash(path), contextSuffix(context))); + default: + throw new IllegalStateException(); + } + } + + /** + * Creates a response to return to the client for a request that was waiting when a failure occurred. + */ + public ResponseProducer failureWhileWaiting(String requestMethod, String path, String waitingFor, String context) { + switch (failure) { + case UnexpectedRequest: + return new RequestConditionFailure(requestMethod, path, String.format("Failed to handle %s %s due to unexpected request %s %s%s", requestMethod, withLeadingSlash(path), unexpectedMethod, withLeadingSlash(unexpectedPath), contextSuffix(context))); + case Timeout: + return new RequestConditionFailure(requestMethod, path, String.format("Failed to handle %s %s due to a timeout %s%s", requestMethod, withLeadingSlash(path), waitingFor, contextSuffix(context))); + default: + throw new IllegalStateException(); + } + } + + /** + * Creates an exception to throw to the test thread that is waiting for some condition + */ + public RuntimeException getWaitFailure(String context) { + switch (failure) { + case UnexpectedRequest: + return new RuntimeException(String.format("Unexpected request %s %s received%s", unexpectedMethod, withLeadingSlash(unexpectedPath), contextSuffix(context))); + case Timeout: + return new RuntimeException(String.format("Timeout waiting for expected requests%s", contextSuffix(context))); + default: + throw new IllegalStateException(); + } + } +} diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/Failure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/Failure.java new file mode 100644 index 0000000000000..be6e4a175b39d --- /dev/null +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/Failure.java @@ -0,0 +1,20 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.test.fixtures.server.http; + +interface Failure extends ResponseProducer { +} diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/RequestConditionFailure.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/RequestConditionFailure.java new file mode 100644 index 0000000000000..6648e6df81d87 --- /dev/null +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/RequestConditionFailure.java @@ -0,0 +1,23 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.test.fixtures.server.http; + +class RequestConditionFailure extends AbstractFailure { + public RequestConditionFailure(String method, String path, String message) { + super(new RuntimeException(message)); + } +} diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ResponseProducer.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ResponseProducer.java index 2585f0c54e68a..bc8c8c95e4bc2 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ResponseProducer.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/ResponseProducer.java @@ -21,8 +21,19 @@ import java.io.IOException; interface ResponseProducer { + default boolean isFailure() { + return false; + } + /** * Called to handle a request. Is *not* called under lock. */ void writeTo(int requestId, HttpExchange exchange) throws IOException; + + /** + * Returns the failure, if any. + */ + default RuntimeException getFailure() { + throw new IllegalStateException(); + } } diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/TrackingHttpHandler.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/TrackingHttpHandler.java index 7d267a74e1091..5643eb71ed7c2 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/TrackingHttpHandler.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/TrackingHttpHandler.java @@ -18,6 +18,7 @@ import com.sun.net.httpserver.HttpExchange; +import javax.annotation.Nullable; import java.util.Collection; interface TrackingHttpHandler { @@ -28,9 +29,10 @@ interface TrackingHttpHandler { * * This method may block until the request is ready to be handled, but must do so using a condition created from the state lock. * - * @throws Exception on failure to handle request. The handler is considered broken. + * @return null when this handler is not expecting any further requests. */ - ResponseProducer selectResponseProducer(int id, HttpExchange exchange) throws Exception; + @Nullable + ResponseProducer selectResponseProducer(int id, HttpExchange exchange); /** * Returns a precondition that asserts that this handler is not expecting any further requests to be released by the test in order to complete. diff --git a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/UnexpectedRequestException.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/UnexpectedRequestException.java index b6c1bc911b916..ad2a0b6b83294 100644 --- a/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/UnexpectedRequestException.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/UnexpectedRequestException.java @@ -16,7 +16,7 @@ package org.gradle.test.fixtures.server.http; -public class UnexpectedRequestException extends AssertionError { +public class UnexpectedRequestException extends RuntimeException { public UnexpectedRequestException(String message) { super(message, null); } diff --git a/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/packaging/UnrecoverableUnpackingException.java b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/UnexpectedRequestFailure.java similarity index 53% rename from subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/packaging/UnrecoverableUnpackingException.java rename to subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/UnexpectedRequestFailure.java index d341d1d425844..ad11c550d8bfd 100644 --- a/subprojects/build-cache-packaging/src/main/java/org/gradle/caching/internal/packaging/UnrecoverableUnpackingException.java +++ b/subprojects/internal-integ-testing/src/main/groovy/org/gradle/test/fixtures/server/http/UnexpectedRequestFailure.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,19 +14,14 @@ * limitations under the License. */ -package org.gradle.caching.internal.packaging; +package org.gradle.test.fixtures.server.http; -import org.gradle.api.GradleException; - -/** - * Thrown after unpacking failed, when the attempt to clean up unpacked outputs also failed. - */ -public class UnrecoverableUnpackingException extends GradleException { - public UnrecoverableUnpackingException(String message) { - super(message); +class UnexpectedRequestFailure extends AbstractFailure { + public UnexpectedRequestFailure(String method, String path) { + this(method, path, ""); } - public UnrecoverableUnpackingException(String message, Throwable cause) { - super(message, cause); + public UnexpectedRequestFailure(String method, String path, String context) { + super(new UnexpectedRequestException(String.format("Unexpected request %s %s received%s", method, withLeadingSlash(path), contextSuffix(context)))); } } diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailureTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailureTest.groovy index feb6cf40ba4c4..fec2b05f35722 100644 --- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailureTest.groovy +++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/executer/OutputScrapingExecutionFailureTest.groovy @@ -356,6 +356,13 @@ Some.Failure failure.assertOutputContains("Some sort of output") failure.assertOutputContains "Some more output" + and: + !failure.mainContent.withNormalizedEol().contains("INITIALIZING") + !failure.mainContent.withNormalizedEol().contains("IDLE") + + and: + !failure.mainContent.withNormalizedEol().contains("DEBUG") + where: output << [rawOutput, debugOutput] } @@ -393,11 +400,11 @@ WARNING: All illegal access operations will be denied in a future release def static getRawOutput() { return """ -\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001BSome sort of output\u001B[0K -Some sort of FAILURE: without status bar or work in progress +\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> Evaluating settings\u001B[m\u001B[21D\u001B[1B\u001B[2ASome sort of output\u001B[0K +Some sort of FAILURE: without status bar or work in progress\u001B[0K Some more output -\u001B[2A\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% EXECUTING [2s]\u001B[m\u001B[33D\u001B[1B> IDLE\u001B[6D\u001B[1B\u001B[2AFAILURE: \u001B[39m\u001B[31mBuild failed with an exception. \u001B[39m\u001B[0K - +\u001B[1m<\u001B[0;32;1;0;39;1m-------------> 0% EXECUTING [2s]\u001B[m\u001B[33D\u001B[1B> IDLE\u001B[6D\u001B[1B\u001B[2AFAILURE: \u001B[39m\u001B[31mBuild failed with an exception. \u001B[39m\u001B[0K +\u001B[0K * Where: Build file 'build.gradle' line: 4 diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/logging/GroupedOutputFixtureTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/logging/GroupedOutputFixtureTest.groovy index 32e7e65e2ef54..297d2df9815ac 100644 --- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/logging/GroupedOutputFixtureTest.groovy +++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/integtests/fixtures/logging/GroupedOutputFixtureTest.groovy @@ -16,6 +16,7 @@ package org.gradle.integtests.fixtures.logging +import org.gradle.integtests.fixtures.executer.LogContent import spock.lang.Specification class GroupedOutputFixtureTest extends Specification { @@ -44,7 +45,7 @@ Handles lots of newline characters \u001B[2K """ when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.taskCount == 3 @@ -56,21 +57,22 @@ Handles lots of newline characters def "parses incremental tasks"() { given: def consoleOutput = """ -\u001B[2A\u001B[1m<-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> settings\u001B[m\u001B[10D\u001B[1B\u001B[1A\u001B[90m> IDLE\u001B[39m\u001B[0K\u001B[6D\u001B[1B\u001B[2A\u001B[1m<-------------> 0% CONFIGURING [0s]\u001B[m\u001B[0K\u001B[35D\u001B[2B\u001B[1A\u001B[1m> root project\u001B[m\u001B[14D\u001B[1B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [0s]\u001B[m\u001B[0K\u001B[33D\u001B[1B\u001B[90m> IDLE\u001B[39m\u001B[0K\u001B[6D\u001B[1B\u001B[1A\u001B[1m> :longRunningTask\u001B[m\u001B[6D\u001B[1B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [1s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [2s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [3s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [4s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [5s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[0K +\u001B[1m<-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> settings\u001B[m\u001B[10D\u001B[1B\u001B[1A\u001B[90m> IDLE\u001B[39m\u001B[0K\u001B[6D\u001B[1B\u001B[2A\u001B[1m<-------------> 0% CONFIGURING [0s]\u001B[m\u001B[0K\u001B[35D\u001B[2B\u001B[1A\u001B[1m> root project\u001B[m\u001B[14D\u001B[1B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [0s]\u001B[m\u001B[0K\u001B[33D\u001B[1B\u001B[90m> IDLE\u001B[39m\u001B[0K\u001B[6D\u001B[1B\u001B[1A\u001B[1m> :longRunningTask\u001B[m\u001B[18D\u001B[1B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [1s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [2s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [3s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [4s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [5s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[0K \u001B[1m> Task :longRunningTask\u001B[m\u001B[0K First incremental output \u001B[0K \u001B[0K -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [6s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :longRunningTask\u001B[m\u001B[6D\u001B[1B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [7s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [8s]\u001B[m\u001B[33D\u001B[2B\u001B[2ASecond incremental output\u001B[0K -\u001B[1B\u001B[0K -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [8s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :longRunningTask\u001B[m\u001B[6D\u001B[1B\u001B[2A\u001B[0K +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [6s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :longRunningTask\u001B[m\u001B[18D\u001B[1B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [7s]\u001B[m\u001B[33D\u001B[2B\u001B[2A\u001B[1m<-------------> 0% EXECUTING [8s]\u001B[m\u001B[33D\u001B[2B\u001B[2ASecond incremental output\u001B[0K +\u001B[0K +\u001B[0K +\u001B[1A\u001B[1m<-------------> 0% EXECUTING [8s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :longRunningTask\u001B[m\u001B[18D\u001B[1B\u001B[2A\u001B[0K \u001B[0K \u001B[32;1mBUILD SUCCESSFUL\u001B[0;39m in 9s 1 actionable task: 1 executed \u001B[2K """ when: - GroupedOutputFixture fixture = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture fixture = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: fixture.taskCount == 1 @@ -98,7 +100,7 @@ Output from 3 \u001B[2K """ when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.taskCount == 3 @@ -134,7 +136,7 @@ Last line of text """ when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.task(':log').output == 'First line of text\n\n\n\nLast line of text' @@ -155,7 +157,7 @@ Output from 1 \u001B[2K """ when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.task(':1:log').output == "Output from 1" @@ -184,7 +186,7 @@ Output from 2 \u001B[2K """ when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.taskCount == 3 @@ -208,7 +210,7 @@ Output from 2 3 actionable tasks: 3 executed \u001B[2K """ - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) when: groupedOutput.task(':doesNotExist') @@ -233,7 +235,7 @@ Output from :log \u001B[2K """ when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.task(':log').output == 'Output from :log' @@ -241,29 +243,26 @@ Output from :log def "handles output interleaved with end-of-line erasing"() { given: - def consoleOutput = """\u001B[1A\u001B[1m> Connecting to Daemon\u001B[m\u001B[22D\u001B[1B -\u001B[1A\u001B[90m> IDLE\u001B[39m\u001B[0K\u001B[6D\u001B[1B -\u001B[2A\u001B[1m<-------------> 0% CONFIGURING [0s]\u001B[m\u001B[35D\u001B[1B\u001B[1m> root project\u001B[m\u001B[14D\u001B[1B -\u001B[1A\u001B[1m> root project > Compiling /home/tcagent2/agent/work/1c72cb73edd79150/subprojects/logging/build/tmp/test files/BasicGroupedTaskLoggingFunctionalSpec/long_running_task_o..._5s_delay/m5mh7/build.gradle into local compilation cache\u001B[m\u001B[228D\u001B[1B -\u001B[2A\u001B[1m<-------------> 0% CONFIGURING [1s]\u001B[m\u001B[35D\u001B[2B -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [1s]\u001B[m\u001B[0K\u001B[33D\u001B[1B\u001B[1m> :log\u001B[m\u001B[0K\u001B[6D\u001B[1B -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [2s]\u001B[m\u001B[33D\u001B[2B -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [3s]\u001B[m\u001B[33D\u001B[2B -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [4s]\u001B[m\u001B[33D\u001B[2B -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [5s]\u001B[m\u001B[33D\u001B[2B -\u001B[2A\u001B[0K -\u001B[1B\u001B[0K -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [6s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :log\u001B[m\u001B[6D\u001B[1B + def consoleOutput = """\u001B[1A\u001B[1m> Connecting to Daemon\u001B[m\u001B[22D +\u001B[1A\u001B[90m> IDLE\u001B[39m\u001B[0K\u001B[6D +\u001B[1A\u001B[1m<-------------> 0% CONFIGURING [0s]\u001B[m\u001B[35D\u001B[1B\u001B[1m> root project\u001B[m\u001B[14D +\u001B[1A\u001B[1m> root project > Compiling /home/tcagent2/agent/work/1c72cb73edd79150/subprojects/logging/build/tmp/test files/BasicGroupedTaskLoggingFunctionalSpec/long_running_task_o..._5s_delay/m5mh7/build.gradle into local compilation cache\u001B[m\u001B[228D +\u001B[2A\u001B[1m<-------------> 0% CONFIGURING [1s]\u001B[m\u001B[35D\u001B[1B +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [1s]\u001B[m\u001B[0K\u001B[33D\u001B[1B\u001B[1m> :log\u001B[m\u001B[0K\u001B[6D +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [2s]\u001B[m\u001B[33D\u001B[1B +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [3s]\u001B[m\u001B[33D\u001B[1B +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [4s]\u001B[m\u001B[33D\u001B[1B \u001B[2A\u001B[1m> Task :log\u001B[m\u001B[0K Before\u001B[0K \u001B[0K \u001B[0K -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [6s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :log\u001B[m\u001B[6D\u001B[1B -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [7s]\u001B[m\u001B[33D\u001B[2B -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [8s]\u001B[m\u001B[33D\u001B[2B +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [6s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :log\u001B[m\u001B[6D +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [7s]\u001B[m\u001B[33D\u001B[1B +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [8s]\u001B[m\u001B[33D\u001B[1B \u001B[2AAfter\u001B[0K -\u001B[1B\u001B[0K -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [8s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :log\u001B[m\u001B[6D\u001B[1B +\u001B[0K +\u001B[0K +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [8s]\u001B[m\u001B[33D\u001B[1B\u001B[1m> :log\u001B[m\u001B[6D \u001B[2A\u001B[0K \u001B[0K \u001B[32;1mBUILD SUCCESSFUL\u001B[0;39m in 9s @@ -271,7 +270,7 @@ Before\u001B[0K \u001B[2K""" when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.task(':log').output == 'Before\nAfter' @@ -294,7 +293,7 @@ Bye world \u001B[2K""" when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.task(':buildSrc:helloWorld').output == 'Hello world' @@ -316,15 +315,13 @@ Bye world \u001B[2K""" when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.strippedOutput == ''' > Task :buildSrc:helloWorld Hello world - - > Task :byeWorld Bye world @@ -336,17 +333,17 @@ BUILD SUCCESSFUL in 0s def "strip output removes formatted work in-progress items"() { def consoleOutput = """ -\u001B[2A\u001B[1m<-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> settings\u001B[m\u001B[10D\u001B[1B -\u001B[1A> IDLE\u001B[0K\u001B[6D\u001B[1B -\u001B[2A\u001B[1m<-------------> 0% CONFIGURING [1s]\u001B[m\u001B[0K\u001B[35D\u001B[1B\u001B[1m> root project\u001B[m\u001B[14D\u001B[1B -\u001B[2A\u001B[1m<-------------> 0% EXECUTING [2s]\u001B[m\u001B[0K\u001B[33D\u001B[1B> IDLE\u001B[0K\u001B[6D\u001B[1B -\u001B[1A\u001B[1m> :run\u001B[m\u001B[6D\u001B[1B +\u001B[2A\u001B[1m<-------------> 0% INITIALIZING [0s]\u001B[m\u001B[36D\u001B[1B\u001B[1m> settings\u001B[m\u001B[10D +\u001B[1A> IDLE\u001B[0K\u001B[6D +\u001B[2A\u001B[1m<-------------> 0% CONFIGURING [1s]\u001B[m\u001B[0K\u001B[35D\u001B[1B\u001B[1m> root project\u001B[m\u001B[14D +\u001B[2A\u001B[1m<-------------> 0% EXECUTING [2s]\u001B[m\u001B[0K\u001B[33D\u001B[1B> IDLE\u001B[0K\u001B[6D +\u001B[1A\u001B[1m> :run\u001B[m\u001B[6D \u001B[2A\u001B[0K \u001B[1m> Task :run\u001B[m\u001B[0K Hello, World! \u001B[0K \u001B[0K -\u001B[2A\u001B[1m<=============> 100% EXECUTING [3s]\u001B[m\u001B[35D\u001B[1B> IDLE\u001B[6D\u001B[1B +\u001B[2A\u001B[1m<=============> 100% EXECUTING [3s]\u001B[m\u001B[35D\u001B[1B> IDLE\u001B[6D \u001B[2A\u001B[0K \u001B[0K \u001B[32;1mBUILD SUCCESSFUL\u001B[0;39m in 6s @@ -354,11 +351,10 @@ Hello, World! \u001B[2K""" when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.strippedOutput == ''' - > Task :run Hello, World! @@ -377,7 +373,7 @@ Hello, World! > :otherBuild > :abc""" when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.task(':helloWorld').output == 'Hello, World!' @@ -391,7 +387,7 @@ Hello, World! > :otherBuild > Doing some work""" when: - GroupedOutputFixture groupedOutput = new GroupedOutputFixture(consoleOutput) + GroupedOutputFixture groupedOutput = new GroupedOutputFixture(LogContent.of(consoleOutput)) then: groupedOutput.task(':helloWorld').output == 'Hello, World!' @@ -406,7 +402,7 @@ Hello, World! [3A [1m< [0;32;1;0;39;1m-------------> 0% CONFIGURING [5s] [m [35D [1B [1m> root project > Compiling C:\\tcagent1\\work\\4b92f910977a653d\\subprojects\\logging\\build\\tmp\\test files\\ConsoleBuildSrcFunctionalTest\\can_group_task_outp..._buildSrc\\j2q4s\\build.gradle into local compilation cache > Compiling build file 'C:\\tcagent1\\work\\4b92f910977a653d\\subprojects\\logging\\build\\tmp\\test files\\ConsoleBuildSrcFunctionalTest\\can_group_task_outp..._buildSrc\\j2q4s\\build.gradle' to cross build script cache [m [420D [2B """ when: - new GroupedOutputFixture(consoleOutput) + new GroupedOutputFixture(LogContent.of(consoleOutput)) then: noExceptionThrown() diff --git a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServerTest.groovy b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServerTest.groovy index c079d7fb900b5..5548ec3c288d7 100644 --- a/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServerTest.groovy +++ b/subprojects/internal-integ-testing/src/test/groovy/org/gradle/test/fixtures/server/http/BlockingHttpServerTest.groovy @@ -18,6 +18,7 @@ package org.gradle.test.fixtures.server.http import org.gradle.test.fixtures.concurrent.ConcurrentSpec import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider +import org.gradle.util.TestUtil import org.hamcrest.Matchers import org.junit.Rule @@ -263,6 +264,54 @@ class BlockingHttpServerTest extends ConcurrentSpec { instant.aDone > instant.aBlocked } + def "can call from client code"() { + server.start() + def script = TestUtil.createScript """ + def prefix = "a" + ${server.callFromBuild("a1")} + ${server.callFromBuildUsingExpression("prefix + '2'")} + """ + + given: + server.expect("a1") + server.expect("a2") + + when: + script.run() + server.stop() + + then: + noExceptionThrown() + } + + def "client code fails when making unexpected request"() { + server.start() + def script = TestUtil.createScript """ + def prefix = "a" + ${server.callFromBuild("a1")} + ${server.callFromBuildUsingExpression("prefix + '2'")} + """ + + given: + server.expect("a1") + server.expect("other") + + when: + script.run() + + then: + def e = thrown(RuntimeException) + e.message == "Received error response from ${server.uri}/a2" + + when: + server.stop() + + then: + def e2 = thrown(RuntimeException) + e2.message == 'Failed to handle all HTTP requests.' + e2.causes.message == ['Unexpected request GET /a2 received. Waiting for [GET /other], already received []'] + } + def "succeeds when expected concurrent requests are made"() { given: server.expectConcurrent("a", "b", "c") @@ -555,7 +604,7 @@ class BlockingHttpServerTest extends ConcurrentSpec { then: def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' - e.causes.message.sort() == ['Did not receive expected requests. Waiting for [GET /b], received []'] + e.causes.message.sort() == ['Did not receive expected requests. Waiting for [GET /b], already received []'] } def "fails when request is received after serial expectations met"() { @@ -576,7 +625,7 @@ class BlockingHttpServerTest extends ConcurrentSpec { then: def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' - e.causes.message.sort() == ['Received unexpected request GET /a'] + e.causes.message.sort() == ['Unexpected request GET /a received'] } def "fails when request path does not match expected serial request"() { @@ -585,7 +634,14 @@ class BlockingHttpServerTest extends ConcurrentSpec { server.start() when: - server.uri("b").toURL().text + def connection = server.uri("b").toURL().openConnection() + + then: + connection.responseCode == 400 + connection.responseMessage == "Bad Request" + + when: + connection.inputStream then: thrown(IOException) @@ -597,9 +653,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle GET /b' + 'Unexpected request GET /b received. Waiting for [GET /a], already received []' ] - e.causes[0].cause.message == 'Unexpected request GET /b received. Waiting for [GET /a], already received [].' } def "fails when request method does not match expected serial GET request"() { @@ -610,7 +665,13 @@ class BlockingHttpServerTest extends ConcurrentSpec { when: def connection = server.uri("a").toURL().openConnection() connection.requestMethod = 'HEAD' - connection.inputStream.text + + then: + connection.responseCode == 400 + connection.responseMessage == "Bad Request" + + when: + connection.inputStream then: thrown(IOException) @@ -622,9 +683,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle HEAD /a' + 'Unexpected request HEAD /a received. Waiting for [GET /a], already received []' ] - e.causes[0].cause.message == 'Unexpected request HEAD /a received. Waiting for [GET /a], already received [].' } def "fails when request method does not match expected serial PUT request"() { @@ -645,9 +705,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle GET /a' + 'Unexpected request GET /a received. Waiting for [PUT /a], already received []' ] - e.causes[0].cause.message == 'Unexpected request GET /a received. Waiting for [PUT /a], already received [].' } def "fails when request method does not match expected serial POST request"() { @@ -668,9 +727,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message.sort() == [ - 'Failed to handle GET /a' + 'Unexpected request GET /a received. Waiting for [POST /a], already received []' ] - e.causes[0].cause.message == 'Unexpected request GET /a received. Waiting for [POST /a], already received [].' } def "fails when some but not all expected parallel requests received"() { @@ -691,9 +749,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message.sort() == [ - 'Failed to handle GET /a' + 'Failed to handle GET /a due to a timeout waiting for other requests. Waiting for [GET /b], already received [GET /a]' ] - e.causes[0].cause.message == 'Timeout waiting for expected requests to be received. Still waiting for [GET /b], received [GET /a].' } def "fails when expected parallel request received after other request has failed"() { @@ -720,12 +777,9 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle GET /a', - 'Failed to handle GET /b', + 'Failed to handle GET /a due to a timeout waiting for other requests. Waiting for [GET /b], already received [GET /a]', + 'Failed to handle GET /b due to a previous timeout. Waiting for [], already received [GET /a, GET /b]' ] - e.causes[0].cause.message == 'Timeout waiting for expected requests to be received. Still waiting for [GET /b], received [GET /a].' - // TODO - message should say that expected request was received too late - e.causes[1].cause.message == 'Timeout waiting for expected requests to be received. Still waiting for [GET /b], received [GET /a].' } def "fails when some but not all expected parallel requests received when stop called"() { @@ -747,7 +801,7 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Did not receive expected requests. Waiting for [GET /b], received [GET /a]' + 'Did not receive expected requests. Waiting for [GET /b], already received [GET /a]' ] } @@ -769,21 +823,34 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message.sort() == [ - 'Failed to handle GET /c' + 'Unexpected request GET /c received. Waiting for [GET /a, GET /b], already received []' ] - e.causes[0].cause.message == 'Unexpected request GET /c received. Waiting for [GET /a, GET /b], already received [].' } def "fails when request path does not match expected blocking parallel request"() { + def requestFailure = null + given: - server.expectConcurrentAndBlock("a", "b") + def handle = server.expectConcurrentAndBlock("a", "b") server.start() when: - server.uri("c").toURL().text + async { + start { + try { + server.uri("c").toURL().text + } catch (IOException e) { + requestFailure = e + } + } + handle.waitForAllPendingCalls() + } then: - thrown(IOException) + def waitException = thrown(RuntimeException) + waitException.message == 'Unexpected request GET /c received. Waiting for 2 further requests, received [], released [], not yet received [GET /a, GET /b]' + + requestFailure instanceof IOException when: server.stop() @@ -792,9 +859,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message.sort() == [ - 'Failed to handle GET /c' + 'Unexpected request GET /c received. Waiting for 2 further requests, received [], released [], not yet received [GET /a, GET /b]' ] - e.causes[0].cause.message == 'Unexpected request GET /c received. Waiting for 2 further requests, already received [], released [], still expecting [GET /a, GET /b].' } def "fails when request method does not match expected parallel request"() { @@ -847,14 +913,10 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle HEAD /a', - 'Failed to handle GET /b', - 'Failed to handle GET /c' + 'Unexpected request HEAD /a received. Waiting for [GET /a, GET /b, PUT /c], already received []', + 'Failed to handle GET /b due to unexpected request HEAD /a. Waiting for [GET /a, PUT /c], already received [GET /b]', + 'Unexpected request GET /c received. Waiting for [GET /a, PUT /c], already received [GET /b]' ] - e.causes[0].cause.message == 'Unexpected request HEAD /a received. Waiting for [GET /a, GET /b, PUT /c], already received [].' - // TODO - message should indicate GET /b was received at the time the failure happened - e.causes[1].cause.message == 'Unexpected request HEAD /a received. Waiting for [GET /a, GET /b, PUT /c], already received [].' - e.causes[2].cause.message == 'Unexpected request GET /c received. Waiting for [GET /a, PUT /c], already received [GET /b].' } def "fails when request method does not match expected blocking parallel request"() { @@ -863,7 +925,7 @@ class BlockingHttpServerTest extends ConcurrentSpec { def failure3 = null given: - server.expectConcurrentAndBlock(server.get("a"), server.get("b"), server.put("c")) + def handle = server.expectConcurrentAndBlock(server.get("a"), server.get("b"), server.put("c")) server.start() when: @@ -893,9 +955,14 @@ class BlockingHttpServerTest extends ConcurrentSpec { failure3 = t } } + server.waitForRequests(3) + handle.waitForAllPendingCalls() } then: + def waitException = thrown(RuntimeException) + waitException.message == 'Unexpected request HEAD /a received. Waiting for 2 further requests, received [GET /b], released [], not yet received [GET /a, PUT /c]' + failure1 instanceof IOException failure2 instanceof IOException failure3 instanceof IOException @@ -907,15 +974,10 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle HEAD /a', - 'Failed to handle GET /b', - 'Failed to handle GET /c', + 'Unexpected request HEAD /a received. Waiting for 3 further requests, received [], released [], not yet received [GET /a, GET /b, PUT /c]', + 'Failed to handle GET /b due to unexpected request HEAD /a. Waiting for 2 further requests, received [GET /b], released [], not yet received [GET /a, PUT /c]', + 'Unexpected request GET /c received. Waiting for 2 further requests, received [GET /b], released [], not yet received [GET /a, PUT /c]', ] - e.causes[0].cause.message == 'Unexpected request HEAD /a received. Waiting for 3 further requests, already received [], released [], still expecting [GET /a, GET /b, PUT /c].' - // TODO - message should indicate GET /b was received at the time the failure happened - e.causes[1].cause.message == 'Unexpected request HEAD /a received. Waiting for 3 further requests, already received [], released [], still expecting [GET /a, GET /b, PUT /c].' - // TODO - message should indicate GET /b was received at the time the failure happened - e.causes[2].cause.message == 'Unexpected request GET /c received. Waiting for 2 further requests, already received [GET /b], released [], still expecting [GET /a, PUT /c].' } def "fails when additional requests are made after parallel expectations are met"() { @@ -939,8 +1001,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { then: def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' - e.cause.message == 'Received unexpected request GET /c' - e.causes.message.sort() == ['Received unexpected request GET /c'] + e.cause.message == 'Unexpected request GET /c received' + e.causes.message.sort() == ['Unexpected request GET /c received'] } def "fails when some but not all expected parallel requests received while waiting"() { @@ -963,8 +1025,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { } then: - def waitError = thrown(AssertionError) - waitError.message == 'Timeout waiting for expected requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c].' + def waitError = thrown(RuntimeException) + waitError.message == 'Timeout waiting for expected requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c]' requestFailure instanceof IOException @@ -975,9 +1037,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle GET /a' + 'Failed to handle GET /a due to a timeout waiting for other requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c]' ] - e.causes[0].cause.message == 'Timeout waiting for expected requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c].' } def "fails when expected parallel request received after waiting has failed"() { @@ -1000,8 +1061,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { } then: - def waitError = thrown(AssertionError) - waitError.message == 'Timeout waiting for expected requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c].' + def waitError = thrown(RuntimeException) + waitError.message == 'Timeout waiting for expected requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c]' requestFailure instanceof IOException @@ -1018,12 +1079,9 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle GET /a', - 'Failed to handle GET /b' + 'Failed to handle GET /a due to a timeout waiting for other requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c]', + 'Failed to handle GET /b due to a previous timeout. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c]' ] - e.causes[0].cause.message == 'Timeout waiting for expected requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c].' - // TODO - message should indicate that request received after timeout - e.causes[1].cause.message == 'Timeout waiting for expected requests. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c].' } def "fails when unexpected request received while other request is waiting "() { @@ -1058,11 +1116,9 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle GET /a', - 'Failed to handle GET /c' + 'Failed to handle GET /a due to unexpected request GET /c. Waiting for [GET /b], already received [GET /a]', + 'Unexpected request GET /c received. Waiting for [GET /b], already received [GET /a]' ] - e.causes[0].cause.message == 'Unexpected request GET /c received. Waiting for [GET /b], already received [GET /a].' - e.causes[1].cause.message == 'Unexpected request GET /c received. Waiting for [GET /b], already received [GET /a].' failure1 instanceof IOException failure2 instanceof IOException @@ -1097,8 +1153,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { } then: - def waitError = thrown(AssertionError) - waitError.message == "Unexpected request GET /d received. Waiting for 1 further requests, already received [GET /a], released [], still expecting [GET /b, GET /c]." + def waitError = thrown(RuntimeException) + waitError.message == "Unexpected request GET /d received. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c]" failure1 instanceof IOException failure2 instanceof IOException @@ -1110,11 +1166,9 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle GET /a', - 'Failed to handle GET /d' + 'Failed to handle GET /a due to unexpected request GET /d. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c]', + 'Unexpected request GET /d received. Waiting for 1 further requests, received [GET /a], released [], not yet received [GET /b, GET /c]' ] - e.causes[0].cause.message == 'Unexpected request GET /d received. Waiting for 1 further requests, already received [GET /a], released [], still expecting [GET /b, GET /c].' - e.causes[1].cause.message == 'Unexpected request GET /d received. Waiting for 1 further requests, already received [GET /a], released [], still expecting [GET /b, GET /c].' } def "fails when too many concurrent requests received while waiting"() { @@ -1156,8 +1210,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { } then: - def waitError = thrown(AssertionError) - waitError.message == "Unexpected request GET /c received. Waiting for 0 further requests, already received [GET /a, GET /b], released [], still expecting [GET /c]." + def waitError = thrown(RuntimeException) + waitError.message == "Unexpected request GET /c received. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c]" failure1 instanceof IOException failure2 instanceof IOException @@ -1169,15 +1223,11 @@ class BlockingHttpServerTest extends ConcurrentSpec { then: def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' + // TODO - message should indicate that /c was expected but there were too many concurrent requests e.causes.message.sort() == [ - 'Failed to handle GET /a', - 'Failed to handle GET /b', - 'Failed to handle GET /c', - ] - e.causes.cause.message.sort() == [ - "Unexpected request GET /c received. Waiting for 0 further requests, already received [GET /a, GET /b], released [], still expecting [GET /c].", - "Unexpected request GET /c received. Waiting for 0 further requests, already received [GET /a, GET /b], released [], still expecting [GET /c].", - "Unexpected request GET /c received. Waiting for 0 further requests, already received [GET /a, GET /b], released [], still expecting [GET /c]." + 'Failed to handle GET /a due to unexpected request GET /c. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c]', + 'Failed to handle GET /b due to unexpected request GET /c. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c]', + 'Unexpected request GET /c received. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c]', ] } @@ -1216,9 +1266,9 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e4 = thrown(RuntimeException) e4.message == 'Failed to handle all HTTP requests.' e4.causes.message == [ - 'Did not handle all expected requests. Waiting for 2 further requests, received [], released [], not yet received [GET /a, GET /b].', - 'Did not handle all expected requests. Waiting for 2 further requests, received [], released [], not yet received [GET /c, GET /d].', - 'Did not receive expected requests. Waiting for [GET /e], received []' + 'Did not handle all expected requests. Waiting for 2 further requests, received [], released [], not yet received [GET /a, GET /b]', + 'Did not handle all expected requests. Waiting for 2 further requests, received [], released [], not yet received [GET /c, GET /d]', + 'Did not receive expected requests. Waiting for [GET /e], already received []' ] } @@ -1242,8 +1292,8 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e2 = thrown(RuntimeException) e2.message == 'Failed to handle all HTTP requests.' e2.causes.message == [ - 'Did not handle all expected requests. Waiting for 2 further requests, received [], released [], not yet received [GET /a, GET /b].', - 'Did not handle all expected requests. Waiting for 2 further requests, received [], released [], not yet received [GET /c, GET /d].', + 'Did not handle all expected requests. Waiting for 2 further requests, received [], released [], not yet received [GET /a, GET /b]', + 'Did not handle all expected requests. Waiting for 2 further requests, received [], released [], not yet received [GET /c, GET /d]', ] } @@ -1287,11 +1337,9 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e = thrown(RuntimeException) e.message == 'Failed to handle all HTTP requests.' e.causes.message == [ - 'Failed to handle GET /a', - 'Failed to handle GET /b' + 'Failed to handle GET /a due to a timeout waiting to be released. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c]', + 'Failed to handle GET /b due to a timeout waiting to be released. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c]' ] - e.causes[0].cause.message == 'Timeout waiting to be released. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c].' - e.causes[1].cause.message == 'Timeout waiting to be released. Waiting for 0 further requests, received [GET /a, GET /b], released [], not yet received [GET /c].' } def "fails when request is not released after sending partial response"() { @@ -1356,9 +1404,9 @@ class BlockingHttpServerTest extends ConcurrentSpec { def e3 = thrown(RuntimeException) e3.message == 'Failed to handle all HTTP requests.' e3.causes.message == [ - 'Did not receive expected requests. Waiting for [GET /a], received []', - 'Did not handle all expected requests. Waiting for 1 further requests, received [], released [], not yet received [GET /b].', - 'Did not receive expected requests. Waiting for [GET /c], received []' + 'Did not receive expected requests. Waiting for [GET /a], already received []', + 'Did not handle all expected requests. Waiting for 1 further requests, received [], released [], not yet received [GET /b]', + 'Did not receive expected requests. Waiting for [GET /c], already received []' ] } diff --git a/subprojects/internal-performance-testing/internal-performance-testing.gradle.kts b/subprojects/internal-performance-testing/internal-performance-testing.gradle.kts index 5edb56b2b0b86..c83fc49ae1745 100644 --- a/subprojects/internal-performance-testing/internal-performance-testing.gradle.kts +++ b/subprojects/internal-performance-testing/internal-performance-testing.gradle.kts @@ -44,6 +44,7 @@ dependencies { compile(library("commons_math")) compile(library("jcl_to_slf4j")) compile("org.openjdk.jmc:flightrecorder:7.0.0-SNAPSHOT") + compile("org.gradle.ci.health:tagging:0.63") runtime("com.h2database:h2:1.4.192") } @@ -69,4 +70,4 @@ tasks.jar { testFixtures { from(":core", "main") from(":toolingApi", "main") -} \ No newline at end of file +} diff --git a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/generator/FileContentGenerator.groovy b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/generator/FileContentGenerator.groovy index d6beac944176d..b5cc55d3f25bc 100644 --- a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/generator/FileContentGenerator.groovy +++ b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/generator/FileContentGenerator.groovy @@ -112,27 +112,18 @@ abstract class FileContentGenerator { } def generatePomXML(Integer subProjectNumber, DependencyTree dependencyTree) { - def body + def body = "" + def isParent = subProjectNumber == null || config.subProjects == 0 def hasSources = subProjectNumber != null || config.subProjects == 0 - if (!hasSources) { - body = """ - - ${(0..config.subProjects - 1).collect { "project$it" }.join("\n ")} - - """ - } else { - def subProjectNumbers = dependencyTree.getChildProjectIds(subProjectNumber) - def subProjectDependencies = '' - if (subProjectNumbers?.size() > 0) { - subProjectDependencies = subProjectNumbers.collect { convertToPomDependency("org.gradle.test.performance:project$it:1.0") }.join() + if (isParent) { + if (config.subProjects != 0) { + body += """ + + ${(0..config.subProjects - 1).collect { "project$it" }.join("\n ")} + + """ } - body = """ - - ${config.externalApiDependencies.collect { convertToPomDependency(it) }.join()} - ${config.externalImplementationDependencies.collect { convertToPomDependency(it) }.join()} - ${convertToPomDependency('junit:junit:4.12', 'test')} - ${subProjectDependencies} - + body += """ @@ -174,6 +165,29 @@ abstract class FileContentGenerator { """ + } else { + body += """ + + org.gradle.test.performance + project + 1.0 + + """ + } + if (hasSources) { + def subProjectNumbers = dependencyTree.getChildProjectIds(subProjectNumber) + def subProjectDependencies = '' + if (subProjectNumbers?.size() > 0) { + subProjectDependencies = subProjectNumbers.collect { convertToPomDependency("org.gradle.test.performance:project$it:1.0") }.join() + } + body += """ + + ${config.externalApiDependencies.collect { convertToPomDependency(it) }.join()} + ${config.externalImplementationDependencies.collect { convertToPomDependency(it) }.join()} + ${convertToPomDependency('junit:junit:4.12', 'test')} + ${subProjectDependencies} + + """ } """ renderScenario(counter.incrementAndGet(), scenario)); } - - private String getTextColorCss(ScenarioBuildResultData scenario, ScenarioBuildResultData.ExecutionData executionData) { if(scenario.isCrossBuild()) { return "text-dark"; diff --git a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessIndexPageGenerator.java b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessIndexPageGenerator.java index 906c316a85dd3..f787272f0f584 100644 --- a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessIndexPageGenerator.java +++ b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessIndexPageGenerator.java @@ -17,6 +17,8 @@ package org.gradle.performance.results; import com.google.common.collect.Sets; +import org.gradle.ci.github.GitHubIssuesClient; +import org.gradle.ci.tagging.flaky.GitHubKnownIssuesProvider; import java.io.File; import java.io.IOException; @@ -39,9 +41,11 @@ public class FlakinessIndexPageGenerator extends AbstractTablePageGenerator { .thenComparing(comparing(ScenarioBuildResultData::isFlaky).reversed()) .thenComparing(comparing(ScenarioBuildResultData::getDifferencePercentage).reversed()) .thenComparing(ScenarioBuildResultData::getScenarioName); + private final FlakinessIssueReporter issueReporter; - public FlakinessIndexPageGenerator(ResultsStore resultsStore, File resultJson) { + public FlakinessIndexPageGenerator(ResultsStore resultsStore, File resultJson, GitHubIssuesClient gitHubIssuesClient) { super(resultsStore, resultJson); + issueReporter = new FlakinessIssueReporter(gitHubIssuesClient, new GitHubKnownIssuesProvider(gitHubIssuesClient)); } @Override @@ -107,4 +111,8 @@ protected void renderScenarioButtons(int index, ScenarioBuildResultData scenario } }; } + + public void reportToIssueTracker() { + scenarios.stream().filter(ScenarioBuildResultData::isFlaky).forEach(issueReporter::report); + } } diff --git a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessIssueReporter.groovy b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessIssueReporter.groovy new file mode 100644 index 0000000000000..5c64b8edee797 --- /dev/null +++ b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessIssueReporter.groovy @@ -0,0 +1,109 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.performance.results + +import groovy.transform.CompileStatic +import groovy.transform.TypeChecked +import groovy.transform.TypeCheckingMode +import org.gradle.ci.common.model.FlakyTest +import org.gradle.ci.github.GitHubIssuesClient +import org.gradle.ci.tagging.flaky.KnownFlakyTestProvider +import org.gradle.performance.results.ScenarioBuildResultData.ExecutionData +import org.kohsuke.github.GHIssue +import org.kohsuke.github.GHIssueState + +import static org.gradle.ci.github.GitHubIssuesClient.CI_TRACKED_FLAKINESS_LABEL +import static org.gradle.ci.github.GitHubIssuesClient.FROM_BOT_PREFIX +import static org.gradle.ci.github.GitHubIssuesClient.MESSAGE_PREFIX +import static org.gradle.ci.github.GitHubIssuesClient.TEST_NAME_PREFIX + +@CompileStatic +class FlakinessIssueReporter { + static final String GITHUB_FIX_IT_LABEL = "fix-it" + private final KnownFlakyTestProvider provider + private final GitHubIssuesClient gitHubIssuesClient + + FlakinessIssueReporter(GitHubIssuesClient gitHubIssuesClient, KnownFlakyTestProvider provider) { + this.provider = provider + this.gitHubIssuesClient = gitHubIssuesClient + } + + void report(ScenarioBuildResultData flakyScenario) { + FlakyTest knownFlakyTest = findKnownFlakyTest(flakyScenario) + if (knownFlakyTest) { + if (issueClosed(knownFlakyTest)) { + knownFlakyTest.issue.reopen() + } + if (!hasFixItLabel(knownFlakyTest)) { + knownFlakyTest.issue.addLabels(GITHUB_FIX_IT_LABEL) + } + } else { + knownFlakyTest = openNewFlakyTestIssue(flakyScenario) + } + + commentCurrentFailureToIssue(flakyScenario, knownFlakyTest.issue) + } + + @TypeChecked(TypeCheckingMode.SKIP) + static void commentCurrentFailureToIssue(ScenarioBuildResultData scenario, GHIssue issue) { + issue.comment(""" +${FROM_BOT_PREFIX} + +Coordinator url: https://builds.gradle.org/viewLog.html?buildId=${System.getenv("BUILD_ID")} +Worker url: ${scenario.webUrl} +Agent: [${scenario.agentName}](${scenario.agentUrl}) +Details: + +| Iteration | Difference | Confidence | +|---|---|---| +${assembleTable(scenario)} +""") + } + + private static String assembleTable(ScenarioBuildResultData scenario) { + scenario.executions.withIndex().collect { ExecutionData execution, int index -> + "|${index + 1}|${execution.differenceDisplay}|${execution.formattedConfidence}|" + }.join('\n') + } + + private FlakyTest openNewFlakyTestIssue(ScenarioBuildResultData flakyScenario) { + String title = "Flaky performance test: ${flakyScenario.flakyIssueTestName}" + String message = "we're slower than" + String body = """ +${FROM_BOT_PREFIX} + +${TEST_NAME_PREFIX}${flakyScenario.flakyIssueTestName} + +${MESSAGE_PREFIX}$message +""" + + GHIssue issue = gitHubIssuesClient.createBuildToolInvalidFailureIssue(title, body, [CI_TRACKED_FLAKINESS_LABEL]) + return new FlakyTest(issue: issue) + } + + private FlakyTest findKnownFlakyTest(ScenarioBuildResultData scenario) { + return provider.knownInvalidFailures.find { scenario.flakyIssueTestName.contains(it.name) } + } + + private static boolean issueClosed(FlakyTest flakyTest) { + return flakyTest.issue.state == GHIssueState.CLOSED + } + + private static boolean hasFixItLabel(FlakyTest flakyTest) { + return flakyTest.issue.getLabels().collect { it.name }.contains(GITHUB_FIX_IT_LABEL) + } +} diff --git a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessReportGenerator.java b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessReportGenerator.java index f13c7b20df7da..413f39c549b1d 100644 --- a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessReportGenerator.java +++ b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/FlakinessReportGenerator.java @@ -16,6 +16,9 @@ package org.gradle.performance.results; +import org.gradle.ci.github.DefaultGitHubIssuesClient; +import org.gradle.ci.github.GitHubIssuesClient; + import java.io.File; import java.io.IOException; @@ -26,7 +29,11 @@ public static void main(String[] args) throws Exception { @Override protected void renderIndexPage(ResultsStore store, File resultJson, File outputDirectory) throws IOException { - new FileRenderer().render(store, new FlakinessIndexPageGenerator(store, resultJson), new File(outputDirectory, "index.html")); + GitHubIssuesClient gitHubIssuesClient = new DefaultGitHubIssuesClient(System.getProperty("githubToken")); + FlakinessIndexPageGenerator reporter = new FlakinessIndexPageGenerator(store, resultJson, gitHubIssuesClient); + new FileRenderer().render(store, reporter, new File(outputDirectory, "index.html")); + + reporter.reportToIssueTracker(); } @Override diff --git a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/ScenarioBuildResultData.groovy b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/ScenarioBuildResultData.groovy index c720ba59ca1ba..acc80f47f2c9b 100644 --- a/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/ScenarioBuildResultData.groovy +++ b/subprojects/internal-performance-testing/src/main/groovy/org/gradle/performance/results/ScenarioBuildResultData.groovy @@ -25,13 +25,20 @@ class ScenarioBuildResultData { private static final int FLAKINESS_DETECTION_THRESHOLD = 99 String teamCityBuildId String scenarioName + String scenarioClass String webUrl + String agentName + String agentUrl String testFailure String status boolean crossBuild List currentBuildExecutions = [] List recentExecutions = [] + String getFlakyIssueTestName() { + return "${scenarioClass}.${scenarioName}" + } + boolean isCrossVersion() { return !crossBuild } diff --git a/subprojects/internal-performance-testing/src/test/groovy/org/gradle/performance/results/FlakinessIssueReporterTest.groovy b/subprojects/internal-performance-testing/src/test/groovy/org/gradle/performance/results/FlakinessIssueReporterTest.groovy new file mode 100644 index 0000000000000..2ea1c0b9e6afb --- /dev/null +++ b/subprojects/internal-performance-testing/src/test/groovy/org/gradle/performance/results/FlakinessIssueReporterTest.groovy @@ -0,0 +1,125 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.performance.results + +import org.gradle.ci.common.model.FlakyTest +import org.gradle.ci.github.GitHubIssuesClient +import org.gradle.ci.tagging.flaky.KnownFlakyTestProvider +import org.kohsuke.github.GHIssue +import org.kohsuke.github.GHIssueState +import spock.lang.Specification +import spock.lang.Subject + +import static org.gradle.ci.github.GitHubIssuesClient.CI_TRACKED_FLAKINESS_LABEL +import static org.gradle.performance.results.FlakinessIssueReporter.GITHUB_FIX_IT_LABEL + +class FlakinessIssueReporterTest extends Specification { + GitHubIssuesClient issuesClient = Mock(GitHubIssuesClient) + KnownFlakyTestProvider flakyTestProvider = Mock(KnownFlakyTestProvider) + + @Subject + FlakinessIssueReporter reporter = new FlakinessIssueReporter(issuesClient, flakyTestProvider) + + ScenarioBuildResultData scenario = new ScenarioBuildResultData( + scenarioName: 'myScenario', + scenarioClass: 'my.AwesomeClass', + webUrl: 'myUrl', + agentName: 'myAgent', + agentUrl: 'myAgentUrl', + currentBuildExecutions: [ + new MockExecutionData(100, 1), + new MockExecutionData(98, -1) + ] + ) + + def 'known flaky issue gets commented, reopened and labeled as fix-it'() { + given: + GHIssue issue = Mock(GHIssue) + 1 * flakyTestProvider.knownInvalidFailures >> [new FlakyTest(name: 'my.AwesomeClass.otherScenario'), new FlakyTest(name: 'my.AwesomeClass.myScenario', issue: issue)] + 1 * issue.state >> GHIssueState.CLOSED + 1 * issue.labels >> [] + + when: + reporter.report(scenario) + + then: + 1 * issue.reopen() + 1 * issue.addLabels(GITHUB_FIX_IT_LABEL) + 1 * issue.comment(""" +FROM-BOT + +Coordinator url: https://builds.gradle.org/viewLog.html?buildId=${System.getenv("BUILD_ID")} +Worker url: myUrl +Agent: [myAgent](myAgentUrl) +Details: + +| Iteration | Difference | Confidence | +|---|---|---| +|1|1.0 %|100.0%| +|2|-1.0 %|98.0%| +""") + } + + def 'new issue is created if none found'() { + given: + GHIssue issue = Mock(GHIssue) + 1 * flakyTestProvider.knownInvalidFailures >> [new FlakyTest(name: 'otherScenario')] + + when: + reporter.report(scenario) + + then: + 1 * issuesClient.createBuildToolInvalidFailureIssue('Flaky performance test: my.AwesomeClass.myScenario', + """ +FROM-BOT + +TEST_NAME: my.AwesomeClass.myScenario + +MESSAGE: we're slower than +""" + , [CI_TRACKED_FLAKINESS_LABEL]) >> issue + 1 * issue.comment(""" +FROM-BOT + +Coordinator url: https://builds.gradle.org/viewLog.html?buildId=${System.getenv("BUILD_ID")} +Worker url: myUrl +Agent: [myAgent](myAgentUrl) +Details: + +| Iteration | Difference | Confidence | +|---|---|---| +|1|1.0 %|100.0%| +|2|-1.0 %|98.0%| +""") + } + + class MockExecutionData extends ScenarioBuildResultData.ExecutionData { + double confidencePercentage + double differencePercentage + + MockExecutionData(double confidencePercentage, double differencePercentage) { + super(System.currentTimeMillis(), "commitId", null, null) + this.confidencePercentage = confidencePercentage + this.differencePercentage = differencePercentage + } + + @Override + String getDifferenceDisplay() { + return "${differencePercentage} %" + } + } +} diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/SetSystemProperties.java b/subprojects/internal-testing/src/main/groovy/org/gradle/util/SetSystemProperties.java index 6b5b644daeb39..382a1848def55 100644 --- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/SetSystemProperties.java +++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/SetSystemProperties.java @@ -19,12 +19,7 @@ import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import java.io.File; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import java.util.Properties; @@ -33,9 +28,9 @@ * A JUnit rule which restores system properties at the end of the test. */ public class SetSystemProperties implements TestRule { - private final static Logger LOGGER = LoggerFactory.getLogger(SetSystemProperties.class); private final Properties properties; private final Map customProperties = new HashMap(); + private static final String[] IMMUTABLE_SYSTEM_PROPERTIES = new String[] {"java.io.tmpdir"}; public SetSystemProperties() { properties = new Properties(); @@ -52,42 +47,19 @@ public Statement apply(final Statement base, Description description) { return new Statement() { @Override public void evaluate() throws Throwable { + for (String immutableProperty : IMMUTABLE_SYSTEM_PROPERTIES) { + if (customProperties.containsKey(immutableProperty)) { + throw new IllegalArgumentException("'" + immutableProperty + "' should not be set via a rule as its value cannot be changed once it is initialized"); + } + } + System.getProperties().putAll(customProperties); - resetTempDirLocation(); try { base.evaluate(); } finally { System.setProperties(properties); - resetTempDirLocation(); } } }; } - - public static void resetTempDirLocation() { - File tmpdirFile = new File(System.getProperty("java.io.tmpdir")); - // reset cache in File.createTempFile - try { - Field tmpdirField = Class.forName("java.io.File$TempDirectory").getDeclaredField("tmpdir"); - makeFinalFieldAccessible(tmpdirField); - tmpdirField.set(null, tmpdirFile); - } catch (Exception e) { - LOGGER.warn("Cannot reset tmpdir field used by java.io.File.createTempFile"); - } - // reset cache in Files.createTempFile - try { - Field tmpdirField = Class.forName("java.nio.file.TempFileHelper").getDeclaredField("tmpdir"); - makeFinalFieldAccessible(tmpdirField); - tmpdirField.set(null, tmpdirFile.toPath()); - } catch (Exception e) { - LOGGER.warn("Cannot reset tmpdir field used by java.nio.file.Files.createTempFile"); - } - } - - private static void makeFinalFieldAccessible(Field field) throws NoSuchFieldException, IllegalAccessException { - field.setAccessible(true); - Field modifiersField = Field.class.getDeclaredField("modifiers"); - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); - } } diff --git a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy index 923219b3f488a..b7e5fb428f08a 100644 --- a/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy +++ b/subprojects/internal-testing/src/main/groovy/org/gradle/util/TestPrecondition.groovy @@ -112,6 +112,12 @@ enum TestPrecondition implements org.gradle.internal.Factory { JDK10_OR_EARLIER({ JavaVersion.current() <= JavaVersion.VERSION_1_10 }), + JDK11_OR_EARLIER({ + JavaVersion.current() <= JavaVersion.VERSION_11 + }), + JDK12_OR_LATER({ + JavaVersion.current() >= JavaVersion.VERSION_12 + }), JDK7_POSIX({ NOT_WINDOWS.fulfilled }), @@ -166,7 +172,8 @@ enum TestPrecondition implements org.gradle.internal.Factory { MSBUILD({ // Simplistic approach at detecting MSBuild by assuming Windows imply MSBuild is present WINDOWS.fulfilled - }) + }), + SUPPORTS_TARGETING_JAVA6({!JDK12_OR_LATER.fulfilled}) /** * A predicate for testing whether the precondition is fulfilled. diff --git a/subprojects/internal-testing/src/test/groovy/org/gradle/util/SetSystemPropertiesTest.groovy b/subprojects/internal-testing/src/test/groovy/org/gradle/util/SetSystemPropertiesTest.groovy deleted file mode 100644 index 47ed9c2166d94..0000000000000 --- a/subprojects/internal-testing/src/test/groovy/org/gradle/util/SetSystemPropertiesTest.groovy +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2016 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.util - - -import org.junit.Rule -import org.junit.rules.TemporaryFolder -import spock.lang.Specification - -import java.nio.file.Files - - -class SetSystemPropertiesTest extends Specification { - @Rule - TemporaryFolder tempRoot - - def originalTempDir - - def setup() { - originalTempDir = System.getProperty('java.io.tmpdir') - } - - def cleanup() { - System.setProperty('java.io.tmpdir', originalTempDir) - SetSystemProperties.resetTempDirLocation() - } - - - def "cached java.io.tmpdir location used by File.createTempFile should get resetted"() { - given: - def tempDir1 = tempRoot.newFolder('temp1') - def tempDir2 = tempRoot.newFolder('temp2') - when: - System.setProperty('java.io.tmpdir', tempDir1.absolutePath) - SetSystemProperties.resetTempDirLocation() - File.createTempFile("file", null).text = 'content' - then: - tempDir1.listFiles().size() == 1 - when: - System.setProperty('java.io.tmpdir', tempDir2.absolutePath) - SetSystemProperties.resetTempDirLocation() - File.createTempFile("file", null).text = 'content' - then: - tempDir2.listFiles().size() == 1 - } - - def "cached java.io.tmpdir location used by Files.createTempFile should get resetted"() { - given: - def tempDir1 = tempRoot.newFolder('temp1') - def tempDir2 = tempRoot.newFolder('temp2') - when: - System.setProperty('java.io.tmpdir', tempDir1.absolutePath) - SetSystemProperties.resetTempDirLocation() - Files.createTempFile("file", null).toFile().text = 'content' - then: - tempDir1.listFiles().size() == 1 - when: - System.setProperty('java.io.tmpdir', tempDir2.absolutePath) - SetSystemProperties.resetTempDirLocation() - Files.createTempFile("file", null).toFile().text = 'content' - then: - tempDir2.listFiles().size() == 1 - } -} diff --git a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy index 79247df04323e..83974a8339a55 100644 --- a/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy +++ b/subprojects/ivy/src/integTest/groovy/org/gradle/api/publish/ivy/IvyPublishJavaIntegTest.groovy @@ -978,6 +978,79 @@ class IvyPublishJavaIntegTest extends AbstractIvyPublishIntegTest { true | true | true } + @Unroll + def "can publish feature variants (optional: #optional)"() { + requiresExternalDependencies = true + + given: + createBuildScripts(""" + configurations { + optionalFeatureImplementation + optionalFeatureRuntimeElements { + extendsFrom optionalFeatureImplementation + canBeResolved = false + canBeConsumed = true + attributes { + attribute(Usage.USAGE_ATTRIBUTE, project.objects.named(Usage, Usage.JAVA_RUNTIME_JARS)) + } + outgoing.capability("org:optional-feature:\${version}") + } + compileClasspath.extendsFrom(optionalFeatureImplementation) + } + + dependencies { + optionalFeatureImplementation 'org.slf4j:slf4j-api:1.7.26' + } + + artifacts { + optionalFeatureRuntimeElements file:file("\$buildDir/other-artifact.jar"), builtBy:'touchFile', classifier: 'optional-feature' + } + + task touchFile { + doLast { + file("\$buildDir/other-artifact.jar") << "test" + } + } + + components.java.addVariantsFromConfiguration(configurations.optionalFeatureRuntimeElements) { + if ($optional) it.mapToOptional() + } + + publishing { + repositories { + ivy { url "${mavenRepo.uri}" } + } + publications { + ivy(IvyPublication) { + from components.java + } + } + } +""") + + when: + succeeds "publish" + + then: + with(javaLibrary.parsedIvy) { + configurations.keySet() == ["default", "compile", "runtime", "optionalFeatureRuntimeElements"] as Set + if (optional) { + configurations["default"].extend == ["runtime", "compile"] as Set + } else { + configurations["default"].extend == ["runtime", "compile", "optionalFeatureRuntimeElements"] as Set + } + configurations["runtime"].extend == null + configurations["optionalFeatureRuntimeElements"].extend == null + + expectArtifact("publishTest", "jar").hasConf(["compile"]) + expectArtifact("publishTest", "jar", "optional-feature").hasConf(["optionalFeatureRuntimeElements"]) + assertConfigurationDependsOn("optionalFeatureRuntimeElements", "org.slf4j:slf4j-api:1.7.26") + } + + where: + optional << [true, false] + } + private void createBuildScripts(def append) { settingsFile << "rootProject.name = 'publishTest' " diff --git a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java index d2a9e1d417039..a7da3c2c59158 100644 --- a/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java +++ b/subprojects/ivy/src/main/java/org/gradle/api/publish/ivy/internal/publication/DefaultIvyPublication.java @@ -44,6 +44,7 @@ import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyPublicationResolver; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.api.internal.attributes.ImmutableAttributesFactory; +import org.gradle.api.internal.component.IvyPublishingAwareContext; import org.gradle.api.internal.component.SoftwareComponentInternal; import org.gradle.api.internal.component.UsageContext; import org.gradle.api.internal.file.FileCollectionFactory; @@ -248,11 +249,16 @@ private void populateFromComponent() { for (UsageContext usageContext : getSortedUsageContexts()) { String conf = mapUsage(usageContext.getName()); configurations.maybeCreate(conf); - configurations.getByName("default").extend(conf); + if (usageContext instanceof IvyPublishingAwareContext) { + if (!((IvyPublishingAwareContext) usageContext).isOptional()) { + configurations.getByName("default").extend(conf); + } + } else { + configurations.getByName("default").extend(conf); + } for (PublishArtifact publishArtifact : usageContext.getArtifacts()) { - if (!artifactsOverridden && !seenArtifacts.contains(publishArtifact)) { - seenArtifacts.add(publishArtifact); + if (!artifactsOverridden && seenArtifacts.add(publishArtifact)) { artifact(publishArtifact).setConf(conf); } } diff --git a/subprojects/kotlin-compiler-embeddable/kotlin-compiler-embeddable.gradle.kts b/subprojects/kotlin-compiler-embeddable/kotlin-compiler-embeddable.gradle.kts index 5b6771b634145..cfa6d2b29aab8 100644 --- a/subprojects/kotlin-compiler-embeddable/kotlin-compiler-embeddable.gradle.kts +++ b/subprojects/kotlin-compiler-embeddable/kotlin-compiler-embeddable.gradle.kts @@ -55,9 +55,8 @@ tasks { dependenciesIncludes.set(mapOf( "jansi-" to listOf("META-INF/native/**", "org/fusesource/jansi/internal/CLibrary*.class") )) - additionalFiles = fileTree(classpathManifest.get().manifestFile.parentFile) { - include(classpathManifest.get().manifestFile.name) - } + additionalRootFiles.from(classpathManifest) + outputFile.set(jar.get().archiveFile) } diff --git a/subprojects/kotlin-dsl-integ-tests/kotlin-dsl-integ-tests.gradle.kts b/subprojects/kotlin-dsl-integ-tests/kotlin-dsl-integ-tests.gradle.kts index 4a3c49a405807..17735bba53663 100644 --- a/subprojects/kotlin-dsl-integ-tests/kotlin-dsl-integ-tests.gradle.kts +++ b/subprojects/kotlin-dsl-integ-tests/kotlin-dsl-integ-tests.gradle.kts @@ -49,6 +49,11 @@ pluginBundles.forEach { tasks { + // TODO:kotlin-dsl + verifyTestFilesCleanup { + enabled = false + } + val testEnvironment by registering { pluginBundles.forEach { dependsOn("$it:publishPluginsToTestRepository") diff --git a/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/GradleApiExtensionsIntegrationTest.kt b/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/GradleApiExtensionsIntegrationTest.kt index c6a8307916175..b6b7fb7cbbfb2 100644 --- a/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/GradleApiExtensionsIntegrationTest.kt +++ b/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/GradleApiExtensionsIntegrationTest.kt @@ -44,7 +44,6 @@ class GradleApiExtensionsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `Kotlin chooses withType extension specialized to container type`() { - withDefaultSettings() withBuildScript(""" open class A @@ -96,7 +95,7 @@ class GradleApiExtensionsIntegrationTest : AbstractPluginIntegrationTest() { } """) - withSettings(""" + withDefaultSettings().appendText(""" buildCache { local(DirectoryBuildCache::class) } @@ -144,15 +143,7 @@ class GradleApiExtensionsIntegrationTest : AbstractPluginIntegrationTest() { requireGradleDistributionOnEmbeddedExecuter() - withDefaultSettingsIn("buildSrc") - - withBuildScriptIn("buildSrc", """ - plugins { - `kotlin-dsl` - } - - $repositoriesBlock - """) + withKotlinBuildSrc() withFile("buildSrc/src/main/kotlin/foo/FooTask.kt", """ package foo @@ -179,7 +170,6 @@ class GradleApiExtensionsIntegrationTest : AbstractPluginIntegrationTest() { tasks.register("foo", FooTask::class) """) - withDefaultSettings() withBuildScript(""" plugins { id("foo.foo") @@ -194,7 +184,6 @@ class GradleApiExtensionsIntegrationTest : AbstractPluginIntegrationTest() { val guh = newDir("guh") - withDefaultSettings() withBuildScript("") executer.withGradleUserHomeDir(guh) diff --git a/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/GradleKotlinDslIntegrationTest.kt b/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/GradleKotlinDslIntegrationTest.kt index f440f2950bae5..95cfaf7989afb 100644 --- a/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/GradleKotlinDslIntegrationTest.kt +++ b/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/GradleKotlinDslIntegrationTest.kt @@ -22,7 +22,6 @@ import okhttp3.mockwebserver.MockWebServer import org.gradle.test.fixtures.file.LeaksFileHandles import org.gradle.kotlin.dsl.embeddedKotlinVersion -import org.gradle.kotlin.dsl.fixtures.AbstractKotlinIntegrationTest import org.gradle.kotlin.dsl.fixtures.DeepThought import org.gradle.kotlin.dsl.fixtures.LightThought import org.gradle.kotlin.dsl.fixtures.ZeroThought @@ -38,7 +37,7 @@ import org.junit.Assert.assertNotEquals import org.junit.Test -class GradleKotlinDslIntegrationTest : AbstractKotlinIntegrationTest() { +class GradleKotlinDslIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `given a buildscript block, it will be used to compute the runtime classpath`() { diff --git a/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/PrecompiledScriptPluginIntegrationTest.kt b/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/PrecompiledScriptPluginIntegrationTest.kt index 4d0f40bbabe60..c06f3f7b3e526 100644 --- a/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/PrecompiledScriptPluginIntegrationTest.kt +++ b/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/PrecompiledScriptPluginIntegrationTest.kt @@ -2,8 +2,9 @@ package org.gradle.kotlin.dsl.integration import org.gradle.kotlin.dsl.fixtures.normalisedPath import org.gradle.test.fixtures.file.LeaksFileHandles - +import org.hamcrest.CoreMatchers.containsString import org.junit.Assert.assertFalse +import org.junit.Assert.assertThat import org.junit.Assert.assertTrue import org.junit.Test @@ -14,19 +15,26 @@ class PrecompiledScriptPluginIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `generated code follows kotlin-dsl coding conventions`() { - withDefaultSettings() withBuildScript(""" plugins { `kotlin-dsl` - id("org.gradle.kotlin-dsl.ktlint-convention") version "0.2.3" + id("org.gradle.kotlin-dsl.ktlint-convention") version "0.3.0" } - repositories { jcenter() } + $repositoriesBlock """) - withFile("src/main/kotlin/plugin-without-package.gradle.kts", "\n") - withFile("src/main/kotlin/plugins/plugin-with-package.gradle.kts", """ - package plugins + withPrecompiledKotlinScript("plugin-without-package.gradle.kts", """ + plugins { + org.gradle.base + } + """) + withPrecompiledKotlinScript("org/gradle/plugins/plugin-with-package.gradle.kts", """ + package org.gradle.plugins + + plugins { + org.gradle.base + } """) build("generateScriptPluginAdapters") @@ -36,7 +44,7 @@ class PrecompiledScriptPluginIntegrationTest : AbstractPluginIntegrationTest() { } @Test - fun `precompiled script plugins adapters generation is cached and relocatable`() { + fun `precompiled script plugins tasks are cached and relocatable`() { requireGradleDistributionOnEmbeddedExecuter() @@ -44,9 +52,8 @@ class PrecompiledScriptPluginIntegrationTest : AbstractPluginIntegrationTest() { val secondLocation = "second-location" val cacheDir = newDir("cache-dir") - withSettingsIn(firstLocation, """ + withDefaultSettingsIn(firstLocation).appendText(""" rootProject.name = "test" - $pluginManagementBlock buildCache { local { directory = file("${cacheDir.normalisedPath}") @@ -68,25 +75,38 @@ class PrecompiledScriptPluginIntegrationTest : AbstractPluginIntegrationTest() { val secondDir = newDir(secondLocation) firstDir.copyRecursively(secondDir) - val generationTask = ":generateScriptPluginAdapters" + val cachedTasks = listOf( + ":extractPrecompiledScriptPluginPlugins", + ":generateExternalPluginSpecBuilders", + ":compilePluginsBlocks", + ":generatePrecompiledScriptPluginAccessors", + ":generateScriptPluginAdapters" + ) + val configurationTask = ":configurePrecompiledScriptDependenciesResolver" + val downstreamKotlinCompileTask = ":compileKotlin" build(firstDir, "classes", "--build-cache").apply { - assertTaskExecuted(generationTask) + cachedTasks.forEach { assertTaskExecuted(it) } + assertTaskExecuted(configurationTask) + assertTaskExecuted(downstreamKotlinCompileTask) } build(firstDir, "classes", "--build-cache").apply { - assertOutputContains("$generationTask UP-TO-DATE") + cachedTasks.forEach { assertOutputContains("$it UP-TO-DATE") } + assertTaskExecuted(configurationTask) + assertOutputContains("$downstreamKotlinCompileTask UP-TO-DATE") } build(secondDir, "classes", "--build-cache").apply { - assertOutputContains("$generationTask FROM-CACHE") + cachedTasks.forEach { assertOutputContains("$it FROM-CACHE") } + assertTaskExecuted(configurationTask) + assertOutputContains("$downstreamKotlinCompileTask FROM-CACHE") } } @Test fun `precompiled script plugins adapters generation clean stale outputs`() { - withDefaultSettings() withBuildScript(""" plugins { `kotlin-dsl` } repositories { jcenter() } @@ -103,4 +123,84 @@ class PrecompiledScriptPluginIntegrationTest : AbstractPluginIntegrationTest() { assertFalse(existing("build/generated-sources/kotlin-dsl-plugins/kotlin/FooPlugin.kt").exists()) assertTrue(existing("build/generated-sources/kotlin-dsl-plugins/kotlin/BarPlugin.kt").isFile) } + + @Test + fun `can apply precompiled script plugin from groovy script`() { + + requireGradleDistributionOnEmbeddedExecuter() + + withKotlinBuildSrc() + withFile("buildSrc/src/main/kotlin/my-plugin.gradle.kts", """ + tasks.register("myTask") {} + """) + + withDefaultSettings() + withFile("build.gradle", """ + plugins { + id 'my-plugin' + } + """) + + build("myTask") + } + + @Test + fun `accessors are available after script body change`() { + + requireGradleDistributionOnEmbeddedExecuter() + + withKotlinBuildSrc() + val myPluginScript = withFile("buildSrc/src/main/kotlin/my-plugin.gradle.kts", """ + plugins { base } + + base.archivesBaseName = "my" + + println("base") + """) + + withDefaultSettings() + withBuildScript(""" + plugins { + `my-plugin` + } + """) + + build("help").apply { + assertThat(output, containsString("base")) + } + + myPluginScript.appendText(""" + + println("modified") + """.trimIndent()) + + build("help").apply { + assertThat(output, containsString("base")) + assertThat(output, containsString("modified")) + } + } + + @Test + fun `accessors are available after re-running tasks`() { + + requireGradleDistributionOnEmbeddedExecuter() + + withKotlinBuildSrc() + withFile("buildSrc/src/main/kotlin/my-plugin.gradle.kts", """ + plugins { base } + + base.archivesBaseName = "my" + """) + + withDefaultSettings() + withBuildScript(""" + plugins { + `my-plugin` + } + """) + + build("clean") + + build("clean", "--rerun-tasks") + } } diff --git a/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/ProjectSchemaAccessorsIntegrationTest.kt b/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/ProjectSchemaAccessorsIntegrationTest.kt index 9e909f732c937..80b04120fe97d 100644 --- a/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/ProjectSchemaAccessorsIntegrationTest.kt +++ b/subprojects/kotlin-dsl-integ-tests/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/ProjectSchemaAccessorsIntegrationTest.kt @@ -19,6 +19,7 @@ package org.gradle.kotlin.dsl.integration import org.gradle.kotlin.dsl.fixtures.FoldersDsl import org.gradle.kotlin.dsl.fixtures.FoldersDslExpression import org.gradle.kotlin.dsl.fixtures.containsMultiLineString + import org.gradle.test.fixtures.file.LeaksFileHandles import org.hamcrest.CoreMatchers.allOf @@ -40,8 +41,7 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { requireGradleDistributionOnEmbeddedExecuter() - withSettings(""" - $pluginManagementBlock + withDefaultSettings().appendText(""" include(":sub") """) @@ -114,7 +114,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } } - withDefaultSettings() withBuildScript(""" plugins { id("plugin") } @@ -173,7 +172,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } } - withDefaultSettings() withBuildScript(""" plugins { id("plugin") } @@ -211,7 +209,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } } - withDefaultSettings() withBuildScript(""" plugins { id("plugin") } @@ -258,7 +255,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } } - withDefaultSettings() withBuildScript(""" plugins { id("my.plugin") } @@ -326,7 +322,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } } - withDefaultSettings() withBuildScript(""" plugins { id("my.plugin") } @@ -421,8 +416,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { plugins { id("app-or-lib") } my { name = "kotlin-dsl" } """) - - withDefaultSettings() } build("tasks", "-Pmy=lib") @@ -446,8 +439,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `can configure publishing extension`() { - withDefaultSettings() - withBuildScript(""" plugins { @@ -513,7 +504,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { """) } - withDefaultSettings() withBuildScript(""" plugins { @@ -544,7 +534,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `can access extensions registered by declared plugins via jit accessor`() { - withDefaultSettings() withBuildScript(""" plugins { application } @@ -564,7 +553,7 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `can access configurations registered by declared plugins via jit accessor`() { - withSettings(""" + withDefaultSettings().appendText(""" include("a", "b", "c") """) @@ -656,7 +645,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } """) - withDefaultSettings() withBuildScript(""" plugins { id("my-plugin") @@ -706,7 +694,9 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `accessors tasks applied in a mixed Groovy-Kotlin multi-project build`() { - withSettings("include(\"a\")") + withDefaultSettings().appendText(""" + include("a") + """) withBuildScriptIn("a", "") val aTasks = build(":a:tasks").output @@ -741,7 +731,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } """) - withDefaultSettings() withBuildScript(""" inline fun typeOf(t: T) = T::class.simpleName @@ -794,7 +783,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } """) - withDefaultSettings() withBuildScript(""" plugins { id("mine") @@ -890,7 +878,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } """) - withDefaultSettings() withBuildScript(""" plugins { id("my-plugin") @@ -960,7 +947,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { class MyPrivateConventionImpl : MyConvention """) - withDefaultSettings() withBuildScript(""" plugins { id("my-plugin") @@ -999,7 +985,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `accessors to existing configurations`() { - withDefaultSettings() withBuildScript(""" plugins { java @@ -1024,7 +1009,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `accessors to existing tasks`() { - withDefaultSettings() withBuildScript(""" plugins { java @@ -1053,7 +1037,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `accessors to existing source sets`() { - withDefaultSettings() withBuildScript(""" plugins { java @@ -1093,7 +1076,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { @Test fun `accessors to existing elements of extensions that are containers`() { - withDefaultSettings() withBuildScript(""" plugins { distribution @@ -1125,7 +1107,6 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { (dependencies as ExtensionAware).extensions.create("mine") """) - withDefaultSettings() withBuildScript(""" plugins { `my-plugin` @@ -1141,6 +1122,65 @@ class ProjectSchemaAccessorsIntegrationTest : AbstractPluginIntegrationTest() { } } + @Test + fun `can access project extension of nested type compiled to Java 11`() { + + assumeJava11() + + withFolders { + "buildSrc" { + "src/main/java/build" { + withFile("Java11Plugin.java", """ + package build; + + import org.gradle.api.*; + + public class Java11Plugin implements Plugin { + + public static class Java11Extension {} + + @Override public void apply(Project project) { + project.getExtensions().create("java11", Java11Extension.class); + } + } + """) + } + withFile("settings.gradle.kts") + withFile("build.gradle.kts", """ + plugins { + `java-library` + `java-gradle-plugin` + } + + java { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + gradlePlugin { + plugins { + register("java11") { + id = "java11" + implementationClass = "build.Java11Plugin" + } + } + } + """) + } + } + + withBuildScript(""" + plugins { id("java11") } + + java11 { println(this.javaClass.name) } + """) + + assertThat( + build("-q").output, + containsString("build.Java11Plugin${'$'}Java11Extension") + ) + } + private fun withBuildSrc(contents: FoldersDslExpression) { withFolders { diff --git a/subprojects/kotlin-dsl-plugins/kotlin-dsl-plugins.gradle.kts b/subprojects/kotlin-dsl-plugins/kotlin-dsl-plugins.gradle.kts index da34d30592eb3..46afd8f056473 100644 --- a/subprojects/kotlin-dsl-plugins/kotlin-dsl-plugins.gradle.kts +++ b/subprojects/kotlin-dsl-plugins/kotlin-dsl-plugins.gradle.kts @@ -14,10 +14,11 @@ * limitations under the License. */ +import build.futureKotlin +import build.kotlin +import codegen.GenerateKotlinDslPluginsExtensions import org.gradle.gradlebuild.test.integrationtests.IntegrationTest import org.gradle.gradlebuild.unittestandcompile.ModuleType -import build.futureKotlin -import build.withCompileOnlyGradleApiModulesWithParameterNames import plugins.bundledGradlePlugin plugins { @@ -27,7 +28,7 @@ plugins { description = "Kotlin DSL Gradle Plugins deployed to the Plugin Portal" group = "org.gradle.kotlin" -version = "1.2.3" +version = "1.2.7" base.archivesBaseName = "plugins" @@ -35,10 +36,20 @@ gradlebuildJava { moduleType = ModuleType.INTERNAL } -withCompileOnlyGradleApiModulesWithParameterNames(":pluginDevelopment") +val generatedSourcesDir = layout.buildDirectory.dir("generated-sources/kotlin") + +val generateSources by tasks.registering(GenerateKotlinDslPluginsExtensions::class) { + outputDir.set(generatedSourcesDir) + kotlinDslPluginsVersion.set(project.version) +} + +sourceSets.main { + kotlin.srcDir(files(generatedSourcesDir).builtBy(generateSources)) +} dependencies { compileOnly(project(":kotlinDsl")) + compileOnly(project(":pluginDevelopment")) implementation(futureKotlin("stdlib-jdk8")) implementation(futureKotlin("gradle-plugin")) @@ -99,3 +110,8 @@ integTestTasks.configureEach { tasks.noDaemonIntegTest { enabled = false } + +// TODO:kotlin-dsl +tasks.verifyTestFilesCleanup { + enabled = false +} diff --git a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPluginGradlePluginCrossVersionSmokeTest.kt b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPluginGradlePluginCrossVersionSmokeTest.kt index c9e8802efa926..1af56c36ac008 100644 --- a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPluginGradlePluginCrossVersionSmokeTest.kt +++ b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPluginGradlePluginCrossVersionSmokeTest.kt @@ -80,7 +80,6 @@ class KotlinDslPluginGradlePluginCrossVersionSmokeTest( apply() """) - withDefaultSettings() withBuildScript(""" import org.jetbrains.kotlin.config.KotlinCompilerVersion @@ -100,7 +99,7 @@ class KotlinDslPluginGradlePluginCrossVersionSmokeTest( """) withFile("src/main/kotlin/SomeSource.kt", "fun main(args: Array) {}") - buildWithPlugin("classes").apply { + build("classes").apply { assertThat( output, allOf( diff --git a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPluginTest.kt b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPluginTest.kt index 81f66c601b7cb..808d33cd8ca8e 100644 --- a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPluginTest.kt +++ b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPluginTest.kt @@ -2,11 +2,12 @@ package org.gradle.kotlin.dsl.plugins.dsl import org.gradle.api.internal.DocumentationRegistry -import org.gradle.test.fixtures.file.LeaksFileHandles - import org.gradle.kotlin.dsl.fixtures.AbstractPluginTest import org.gradle.kotlin.dsl.fixtures.containsMultiLineString import org.gradle.kotlin.dsl.fixtures.normalisedPath +import org.gradle.kotlin.dsl.support.expectedKotlinDslPluginsVersion + +import org.gradle.test.fixtures.file.LeaksFileHandles import org.hamcrest.CoreMatchers.containsString import org.hamcrest.CoreMatchers.not @@ -23,10 +24,30 @@ class KotlinDslPluginTest : AbstractPluginTest() { fun setupPluginTest() = requireGradleDistributionOnEmbeddedExecuter() + @Test + fun `warns on unexpected kotlin-dsl plugin version`() { + + // The test applies the in-development version of the kotlin-dsl + // which, by convention, it is always ahead of the version expected by + // the in-development version of Gradle + // (see publishedKotlinDslPluginsVersion in kotlin-dsl.gradle.kts) + withKotlinDslPlugin() + + withDefaultSettings().appendText(""" + rootProject.name = "forty-two" + """) + + val appliedKotlinDslPluginsVersion = futurePluginVersions["org.gradle.kotlin.kotlin-dsl"] + build("help").apply { + assertOutputContains( + "This version of Gradle expects version '$expectedKotlinDslPluginsVersion' of the `kotlin-dsl` plugin but version '$appliedKotlinDslPluginsVersion' has been applied to root project 'forty-two'." + ) + } + } + @Test fun `gradle kotlin dsl api dependency is added`() { - withDefaultSettings() withKotlinDslPlugin() withFile("src/main/kotlin/code.kt", """ @@ -39,7 +60,7 @@ class KotlinDslPluginTest : AbstractPluginTest() { """) - val result = buildWithPlugin("classes") + val result = build("classes") result.assertTaskExecuted(":compileKotlin") } @@ -47,7 +68,6 @@ class KotlinDslPluginTest : AbstractPluginTest() { @Test fun `gradle kotlin dsl api is available for test implementation`() { - withDefaultSettings() withBuildScript(""" plugins { @@ -102,7 +122,6 @@ class KotlinDslPluginTest : AbstractPluginTest() { @Test fun `gradle kotlin dsl api is available in test-kit injected plugin classpath`() { - withDefaultSettings() withBuildScript(""" plugins { @@ -195,7 +214,6 @@ class KotlinDslPluginTest : AbstractPluginTest() { @Test fun `sam-with-receiver kotlin compiler plugin is applied to production code`() { - withDefaultSettings() withKotlinDslPlugin() withFile("src/main/kotlin/code.kt", """ @@ -216,7 +234,7 @@ class KotlinDslPluginTest : AbstractPluginTest() { """) - val result = buildWithPlugin("classes") + val result = build("classes") result.assertTaskExecuted(":compileKotlin") } @@ -290,7 +308,7 @@ class KotlinDslPluginTest : AbstractPluginTest() { private fun withBuildExercisingSamConversionForKotlinFunctions(buildSrcScript: String = "") { - withSettingsIn("buildSrc", pluginManagementBlock) + withDefaultSettingsIn("buildSrc") withBuildScriptIn("buildSrc", """ @@ -327,7 +345,6 @@ class KotlinDslPluginTest : AbstractPluginTest() { } """) - withDefaultSettings() withBuildScript(""" task("test") { @@ -337,20 +354,7 @@ class KotlinDslPluginTest : AbstractPluginTest() { """) } - private - fun withKotlinDslPlugin() { - withBuildScript(""" - - plugins { - `kotlin-dsl` - } - - $repositoriesBlock - - """) - } - private fun outputOf(vararg arguments: String) = - buildWithPlugin(*arguments).output + build(*arguments).output } diff --git a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/embedded/EmbeddedKotlinPluginTest.kt b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/embedded/EmbeddedKotlinPluginTest.kt index 02d4afe14c76b..98fa7db6281b4 100644 --- a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/embedded/EmbeddedKotlinPluginTest.kt +++ b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/embedded/EmbeddedKotlinPluginTest.kt @@ -36,7 +36,6 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { @Test fun `applies the kotlin plugin`() { - withDefaultSettings() withBuildScript(""" plugins { @@ -45,14 +44,14 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { """) - val result = buildWithPlugin("assemble") + val result = build("assemble") result.assertOutputContains(":compileKotlin NO-SOURCE") } @Test fun `adds stdlib and reflect as compile only dependencies`() { - withDefaultSettings() + withBuildScript(""" plugins { @@ -74,13 +73,12 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { """) - buildWithPlugin("assertions") + build("assertions") } @Test fun `all embedded kotlin dependencies are resolvable without any added repository`() { - withDefaultSettings() withBuildScript(""" plugins { @@ -96,7 +94,7 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { """) - val result = buildWithPlugin("dependencies") + val result = build("dependencies") assertThat(result.output, containsString("Embedded Kotlin Repository")) listOf("stdlib", "reflect", "compiler-embeddable").forEach { @@ -107,7 +105,6 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { @Test fun `sources and javadoc of all embedded kotlin dependencies are resolvable with an added repository`() { - withDefaultSettings() withBuildScript(""" plugins { @@ -153,7 +150,7 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { printFileNamesOf() """) - val result = buildWithPlugin("help") + val result = build("help") listOf("stdlib", "reflect").forEach { assertThat(result.output, containsString("kotlin-$it-$embeddedKotlinVersion.jar")) @@ -165,7 +162,6 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { @Test fun `can add embedded dependencies to custom configuration`() { - withDefaultSettings() withBuildScript(""" plugins { @@ -178,7 +174,7 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { configurations["customConfiguration"].files.map { println(it) } """) - val result = buildWithPlugin("dependencies", "--configuration", "customConfiguration") + val result = build("dependencies", "--configuration", "customConfiguration") listOf("stdlib", "reflect").forEach { assertThat(result.output, containsString("org.jetbrains.kotlin:kotlin-$it:$embeddedKotlinVersion")) @@ -190,8 +186,7 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { @LeaksFileHandles("Kotlin Compiler Daemon working directory") fun `can be used with GRADLE_METADATA feature preview enabled`() { - withSettings(""" - $defaultSettingsScript + withDefaultSettings().appendText(""" enableFeaturePreview("GRADLE_METADATA") """) @@ -207,7 +202,7 @@ class EmbeddedKotlinPluginTest : AbstractPluginTest() { withFile("src/main/kotlin/source.kt", """var foo = "bar"""") - val result = buildWithPlugin("assemble") + val result = build("assemble") result.assertTaskExecuted(":compileKotlin") } diff --git a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/AbstractPrecompiledScriptPluginTest.kt b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/AbstractPrecompiledScriptPluginTest.kt new file mode 100644 index 0000000000000..bd546c5fe94af --- /dev/null +++ b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/AbstractPrecompiledScriptPluginTest.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.plugins.precompiled + +import org.gradle.kotlin.dsl.fixtures.AbstractPluginTest +import org.gradle.kotlin.dsl.fixtures.classLoaderFor + +import org.junit.Before + + +open class AbstractPrecompiledScriptPluginTest : AbstractPluginTest() { + + @Before + fun setupPluginTest() { + requireGradleDistributionOnEmbeddedExecuter() + } + + protected + inline fun instantiatePrecompiledScriptOf(target: T, className: String): Any = + loadCompiledKotlinClass(className) + .getConstructor(T::class.java) + .newInstance(target) + + protected + fun loadCompiledKotlinClass(className: String): Class<*> = + classLoaderFor(existing("build/classes/kotlin/main")) + .loadClass(className) +} diff --git a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/KotlinParser.kt b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/KotlinParser.kt new file mode 100644 index 0000000000000..8bf7df0422290 --- /dev/null +++ b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/KotlinParser.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.plugins.precompiled + +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector + +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment + +import org.jetbrains.kotlin.com.intellij.openapi.project.Project +import org.jetbrains.kotlin.com.intellij.openapi.util.Disposer +import org.jetbrains.kotlin.com.intellij.psi.PsiManager +import org.jetbrains.kotlin.com.intellij.testFramework.LightVirtualFile + +import org.jetbrains.kotlin.config.CompilerConfiguration + +import org.jetbrains.kotlin.idea.KotlinFileType + +import org.jetbrains.kotlin.psi.KtFile + + +object KotlinParser { + + fun map(code: String, f: KtFile.() -> T): T = + withProject { f(parse("code.kt", code)) } + + fun Project.parse(name: String, code: String): KtFile = + psiManager.findFile(virtualFile(name, code)) as KtFile + + fun virtualFile(name: String, code: String) = + LightVirtualFile(name, KotlinFileType.INSTANCE, code) + + val Project.psiManager + get() = PsiManager.getInstance(this) + + fun withProject(f: Project.() -> T): T { + val parentDisposable = Disposer.newDisposable() + try { + val project = + KotlinCoreEnvironment.createForProduction( + parentDisposable, + CompilerConfiguration().apply { + put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, TestMessageCollector) + }, + EnvironmentConfigFiles.JVM_CONFIG_FILES + ).project + + return f(project) + } finally { + parentDisposable.dispose() + } + } + + private + object TestMessageCollector : MessageCollector { + override fun clear() = Unit + override fun hasErrors(): Boolean = false + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) { + println("$severity: $message") + } + } +} diff --git a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginAccessorsTest.kt b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginAccessorsTest.kt new file mode 100644 index 0000000000000..053ee4cf112f0 --- /dev/null +++ b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginAccessorsTest.kt @@ -0,0 +1,596 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.plugins.precompiled + +import com.nhaarman.mockito_kotlin.doAnswer +import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.inOrder +import com.nhaarman.mockito_kotlin.mock + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.PluginManager + +import org.gradle.kotlin.dsl.fixtures.FoldersDsl +import org.gradle.kotlin.dsl.fixtures.bytecode.InternalName +import org.gradle.kotlin.dsl.fixtures.bytecode.RETURN +import org.gradle.kotlin.dsl.fixtures.bytecode.internalName +import org.gradle.kotlin.dsl.fixtures.bytecode.publicClass +import org.gradle.kotlin.dsl.fixtures.bytecode.publicDefaultConstructor +import org.gradle.kotlin.dsl.fixtures.bytecode.publicMethod +import org.gradle.kotlin.dsl.fixtures.normalisedPath +import org.gradle.kotlin.dsl.fixtures.pluginDescriptorEntryFor +import org.gradle.kotlin.dsl.support.zipTo + +import org.gradle.test.fixtures.file.LeaksFileHandles + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.containsString +import org.hamcrest.Matchers.empty +import org.hamcrest.Matchers.equalTo +import org.hamcrest.Matchers.not + +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.psi.psiUtil.toVisibility +import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType + +import org.junit.Test + +import java.io.File + + +@LeaksFileHandles("Kotlin Compiler Daemon working directory") +class PrecompiledScriptPluginAccessorsTest : AbstractPrecompiledScriptPluginTest() { + + @Test + fun `can use type-safe accessors with same name but different meaning in sibling plugins`() { + + val externalPlugins = withExternalPlugins() + + withFolders { + "buildSrc" { + withDefaultSettingsIn(relativePath) + withKotlinDslPlugin().appendText( + implementationDependencyOn(externalPlugins) + ) + + withFile("src/main/kotlin/local-app.gradle.kts", """ + plugins { `external-app` } + println("*using " + external.name + " from local-app in " + project.name + "*") + """) + + withFile("src/main/kotlin/local-lib.gradle.kts", """ + plugins { `external-lib` } + println("*using " + external.name + " from local-lib in " + project.name + "*") + """) + } + } + + withDefaultSettings().appendText(""" + include("foo") + include("bar") + """) + + withFolders { + "foo" { + withFile("build.gradle.kts", """ + plugins { `local-app` } + """) + } + "bar" { + withFile("build.gradle.kts", """ + plugins { `local-lib` } + """) + } + } + + assertThat( + build("tasks").output, + allOf( + containsString("*using app from local-app in foo*"), + containsString("*using lib from local-lib in bar*") + ) + ) + } + + private + fun implementationDependencyOn(file: File): String = """ + dependencies { + implementation(files("${file.normalisedPath}")) + } + """ + + @Test + fun `can use type-safe accessors for the Kotlin Gradle plugin extensions`() { + + withKotlinDslPlugin().appendText(""" + dependencies { + implementation(kotlin("gradle-plugin")) + } + """) + + withPrecompiledKotlinScript("kotlin-library.gradle.kts", """ + + plugins { kotlin("jvm") } + + kotlin { } + + tasks.compileKotlin { kotlinOptions { } } + + """) + + build("generatePrecompiledScriptPluginAccessors") + + compileKotlin() + } + + @Test + fun `can use type-safe accessors for plugins applied by sibling plugin`() { + + withKotlinDslPlugin() + + withPrecompiledKotlinScript("my-java-library.gradle.kts", """ + plugins { java } + """) + + withPrecompiledKotlinScript("my-java-module.gradle.kts", """ + plugins { id("my-java-library") } + + java { } + + tasks.compileJava { } + """) + + compileKotlin() + } + + @Test + fun `can use type-safe accessors from scripts with same name but different ids`() { + + val externalPlugins = withExternalPlugins() + + withKotlinDslPlugin() + withKotlinBuildSrc() + withFolders { + "buildSrc" { + existing("build.gradle.kts").appendText( + implementationDependencyOn(externalPlugins) + ) + "src/main/kotlin" { + withFile("app/model.gradle.kts", """ + package app + plugins { `external-app` } + println("*using " + external.name + " from app/model in " + project.name + "*") + """) + withFile("lib/model.gradle.kts", """ + package lib + plugins { `external-lib` } + println("*using " + external.name + " from lib/model in " + project.name + "*") + """) + } + } + } + + withDefaultSettings().appendText(""" + include("lib") + include("app") + """) + + withFolders { + "lib" { + withFile("build.gradle.kts", """ + plugins { lib.model } + """) + } + "app" { + withFile("build.gradle.kts", """ + plugins { app.model } + """) + } + } + + assertThat( + build("tasks").output, + allOf( + containsString("*using app from app/model in app*"), + containsString("*using lib from lib/model in lib*") + ) + ) + } + + @Test + fun `can apply sibling plugin whether it has a plugins block or not`() { + + withKotlinDslPlugin() + + withPrecompiledKotlinScript("no-plugins.gradle.kts", "") + withPrecompiledKotlinScript("java-plugin.gradle.kts", """ + plugins { java } + """) + + withPrecompiledKotlinScript("plugins.gradle.kts", """ + plugins { + id("no-plugins") + id("java-plugin") + } + + java { } + + tasks.compileJava { } + """) + + compileKotlin() + } + + @Test + fun `can apply sibling plugin from another package`() { + + withKotlinDslPlugin() + + withPrecompiledKotlinScript("my/java-plugin.gradle.kts", """ + package my + plugins { java } + """) + + withPrecompiledKotlinScript("plugins.gradle.kts", """ + plugins { id("my.java-plugin") } + + java { } + + tasks.compileJava { } + """) + + compileKotlin() + } + + @Test + fun `generated type-safe accessors are internal`() { + + givenPrecompiledKotlinScript("java-project.gradle.kts", """ + plugins { java } + """) + + val generatedSourceFiles = + existing("build/generated-sources") + .walkTopDown() + .filter { it.isFile } + .toList() + + assertThat( + generatedSourceFiles, + not(empty()) + ) + + data class Declaration( + val packageName: String, + val name: String, + val visibility: Visibility? + ) + + val generatedAccessors = + KotlinParser.run { + withProject { + generatedSourceFiles.flatMap { file -> + parse(file.name, file.readText()).run { + val packageName = packageFqName.asString() + declarations.map { declaration -> + Declaration( + packageName, + declaration.name!!, + declaration.visibilityModifierType()?.toVisibility() + ) + } + } + } + } + } + + assertThat( + "Only the generated Gradle Plugin wrapper is not internal", + generatedAccessors.filterNot { it.visibility == Visibilities.INTERNAL }, + equalTo( + listOf( + Declaration( + "", + "JavaProjectPlugin", + null + ) + ) + ) + ) + } + + @Test + fun `can use core plugin spec builders`() { + + givenPrecompiledKotlinScript("java-project.gradle.kts", """ + + plugins { + java + } + + """) + + val (project, pluginManager) = projectAndPluginManagerMocks() + + instantiatePrecompiledScriptOf( + project, + "Java_project_gradle" + ) + + inOrder(pluginManager) { + verify(pluginManager).apply("org.gradle.java") + verifyNoMoreInteractions() + } + } + + @Test + fun `can use plugin spec builders for plugins in the implementation classpath`() { + + // given: + val pluginId = "my.plugin" + val pluginJar = jarForPlugin(pluginId, "MyPlugin") + + withPrecompiledScriptApplying(pluginId, pluginJar) + assertPrecompiledScriptPluginApplies( + pluginId, + "Plugin_gradle" + ) + } + + private + fun assertPrecompiledScriptPluginApplies(pluginId: String, precompiledScriptClassName: String) { + + compileKotlin() + + val (project, pluginManager) = projectAndPluginManagerMocks() + + instantiatePrecompiledScriptOf( + project, + precompiledScriptClassName + ) + + inOrder(pluginManager) { + verify(pluginManager).apply(pluginId) + verifyNoMoreInteractions() + } + } + + @Test + fun `can use plugin specs with jruby-gradle-plugin`() { + + withKotlinDslPlugin().appendText(""" + dependencies { + compile("com.github.jruby-gradle:jruby-gradle-plugin:1.4.0") + } + """) + + withPrecompiledKotlinScript("plugin.gradle.kts", """ + plugins { + com.github.`jruby-gradle`.base + } + """) + + assertPrecompiledScriptPluginApplies( + "com.github.jruby-gradle.base", + "Plugin_gradle" + ) + } + + @Test + fun `plugin application errors are reported but don't cause the build to fail`() { + + // given: + val pluginId = "invalid.plugin" + val pluginJar = jarWithInvalidPlugin(pluginId, "InvalidPlugin") + + withPrecompiledScriptApplying(pluginId, pluginJar) + + gradleExecuterFor(arrayOf("classes")).withStackTraceChecksDisabled().run().apply { + assertOutputContains("An exception occurred applying plugin request [id: '$pluginId']") + assertOutputContains("'InvalidPlugin' is neither a plugin or a rule source and cannot be applied.") + } + } + + private + fun withPrecompiledScriptApplying(pluginId: String, pluginJar: File) { + + withKotlinDslPlugin().appendText(""" + + dependencies { + implementation(files("${pluginJar.normalisedPath}")) + } + + """) + + withPrecompiledKotlinScript("plugin.gradle.kts", """ + + plugins { $pluginId } + + """) + } + + @Test + fun `can use plugin spec builders in multi-project builds with local and external plugins`() { + + testPluginSpecBuildersInMultiProjectBuildWithPluginsFromPackage(null) + } + + @Test + fun `can use plugin spec builders in multi-project builds with local and external plugins sharing package name`() { + + testPluginSpecBuildersInMultiProjectBuildWithPluginsFromPackage("p") + } + + private + fun testPluginSpecBuildersInMultiProjectBuildWithPluginsFromPackage(packageName: String?) { + + val packageDeclaration = packageName?.let { "package $it" } ?: "" + val packageQualifier = packageName?.let { "$it." } ?: "" + + withProjectRoot(newDir("external-plugins")) { + withFolders { + "external-foo" { + withKotlinDslPlugin() + withFile("src/main/kotlin/external-foo.gradle.kts", """ + $packageDeclaration + println("*external-foo applied*") + """) + } + "external-bar" { + withKotlinDslPlugin() + withFile("src/main/kotlin/external-bar.gradle.kts", """ + $packageDeclaration + println("*external-bar applied*") + """) + } + withDefaultSettingsIn(relativePath).appendText(""" + include("external-foo", "external-bar") + """) + } + build("assemble") + } + + val externalFoo = existing("external-plugins/external-foo/build/libs/external-foo.jar") + val externalBar = existing("external-plugins/external-bar/build/libs/external-bar.jar") + + withFolders { + "buildSrc" { + "local-foo" { + withFile("src/main/kotlin/local-foo.gradle.kts", """ + $packageDeclaration + plugins { $packageQualifier`external-foo` } + """) + withKotlinDslPlugin().appendText(""" + dependencies { + implementation(files("${externalFoo.normalisedPath}")) + } + """) + } + "local-bar" { + withFile("src/main/kotlin/local-bar.gradle.kts", """ + $packageDeclaration + plugins { $packageQualifier`external-bar` } + """) + withKotlinDslPlugin().appendText(""" + dependencies { + implementation(files("${externalBar.normalisedPath}")) + } + """) + } + withDefaultSettingsIn(relativePath).appendText(""" + include("local-foo", "local-bar") + """) + withFile("build.gradle.kts", """ + dependencies { + subprojects.forEach { + runtime(project(it.path)) + } + } + """) + } + } + withBuildScript(""" + plugins { + $packageQualifier`local-foo` + $packageQualifier`local-bar` + } + """) + + assertThat( + build("tasks").output, + allOf( + containsString("*external-foo applied*"), + containsString("*external-bar applied*") + ) + ) + } + + private + fun withExternalPlugins(): File = + withProjectRoot(newDir("external-plugins")) { + withDefaultSettings() + withKotlinDslPlugin() + withFolders { + "src/main/kotlin" { + "extensions" { + withFile("Extensions.kt", """ + open class App { var name: String = "app" } + open class Lib { var name: String = "lib" } + """) + } + withFile("external-app.gradle.kts", """ + extensions.create("external", App::class) + """) + withFile("external-lib.gradle.kts", """ + extensions.create("external", Lib::class) + """) + } + } + build("assemble") + existing("build/libs/external-plugins.jar") + } + + private + fun FoldersDsl.withKotlinDslPlugin(): File = + withKotlinDslPluginIn(relativePath) + + private + val FoldersDsl.relativePath + get() = root.relativeTo(projectRoot).path + + private + fun jarWithInvalidPlugin(id: String, implClass: String): File = + pluginJarWith( + pluginDescriptorEntryFor(id, implClass), + "$implClass.class" to publicClass(InternalName(implClass)) + ) + + private + fun jarForPlugin(id: String, implClass: String): File = + pluginJarWith( + pluginDescriptorEntryFor(id, implClass), + "$implClass.class" to emptyPluginClassNamed(implClass) + ) + + private + fun emptyPluginClassNamed(implClass: String): ByteArray = + publicClass(InternalName(implClass), interfaces = listOf(Plugin::class.internalName)) { + publicDefaultConstructor() + publicMethod("apply", "(Ljava/lang/Object;)V") { + RETURN() + } + } + + private + fun pluginJarWith(vararg entries: Pair): File = + newFile("my.plugin.jar").also { file -> + zipTo(file, entries.asSequence()) + } + + private + fun projectAndPluginManagerMocks(): Pair { + val pluginManager = mock() + val project = mock { + on { getPluginManager() } doReturn pluginManager + on { project } doAnswer { it.mock as Project } + } + return Pair(project, pluginManager) + } +} diff --git a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginTest.kt b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginTemplatesTest.kt similarity index 84% rename from subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginTest.kt rename to subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginTemplatesTest.kt index 3d2473179ab04..8341ae8cde393 100644 --- a/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginTest.kt +++ b/subprojects/kotlin-dsl-plugins/src/integTest/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPluginTemplatesTest.kt @@ -2,6 +2,7 @@ package org.gradle.kotlin.dsl.plugins.precompiled import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.doReturn +import com.nhaarman.mockito_kotlin.inOrder import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.verify @@ -13,34 +14,27 @@ import org.gradle.api.invocation.Gradle import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.bundling.Jar -import org.gradle.test.fixtures.file.LeaksFileHandles - -import org.gradle.kotlin.dsl.fixtures.AbstractPluginTest import org.gradle.kotlin.dsl.fixtures.assertFailsWith import org.gradle.kotlin.dsl.fixtures.assertInstanceOf import org.gradle.kotlin.dsl.fixtures.assertStandardOutputOf -import org.gradle.kotlin.dsl.fixtures.classLoaderFor import org.gradle.kotlin.dsl.fixtures.withFolders import org.gradle.kotlin.dsl.precompile.PrecompiledInitScript import org.gradle.kotlin.dsl.precompile.PrecompiledProjectScript import org.gradle.kotlin.dsl.precompile.PrecompiledSettingsScript +import org.gradle.test.fixtures.file.LeaksFileHandles + import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.containsString import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.MatcherAssert.assertThat -import org.junit.Before import org.junit.Test @LeaksFileHandles("Kotlin Compiler Daemon working directory") -class PrecompiledScriptPluginTest : AbstractPluginTest() { - - @Before - fun setupPluginTest() = - requireGradleDistributionOnEmbeddedExecuter() +class PrecompiledScriptPluginTemplatesTest : AbstractPrecompiledScriptPluginTest() { @Test fun `Project scripts from regular source-sets are compiled via the PrecompiledProjectScript template`() { @@ -51,14 +45,22 @@ class PrecompiledScriptPluginTest : AbstractPluginTest() { """) - val project = mock() + val task = mock() + val project = mock { + on { task(any()) } doReturn task + } assertInstanceOf( instantiatePrecompiledScriptOf( project, - "My_project_script_gradle")) + "My_project_script_gradle" + ) + ) - verify(project).task("my-task") + inOrder(project, task) { + verify(project).task("my-task") + verifyNoMoreInteractions() + } } @Test @@ -105,7 +107,6 @@ class PrecompiledScriptPluginTest : AbstractPluginTest() { // given: val expectedMessage = "Not on my watch!" - withDefaultSettings() withKotlinDslPlugin() withFile("src/main/kotlin/my-project-script.gradle.kts", """ @@ -193,15 +194,12 @@ class PrecompiledScriptPluginTest : AbstractPluginTest() { """) } - withFile("settings.gradle.kts", """ - - $pluginManagementBlock - - """) + withFile("settings.gradle.kts", defaultSettingsScript) withFile( "build.gradle.kts", - scriptWithKotlinDslPlugin()) + scriptWithKotlinDslPlugin() + ) } } @@ -261,11 +259,7 @@ class PrecompiledScriptPluginTest : AbstractPluginTest() { """) } - withFile("settings.gradle.kts", """ - - $pluginManagementBlock - - """) + withFile("settings.gradle.kts", defaultSettingsScript) withFile("build.gradle.kts", """ @@ -360,43 +354,4 @@ class PrecompiledScriptPluginTest : AbstractPluginTest() { ) } } - - private - fun givenPrecompiledKotlinScript(fileName: String, code: String) { - withDefaultSettings() - withKotlinDslPlugin() - withFile("src/main/kotlin/$fileName", code) - compileKotlin() - } - - private - inline fun instantiatePrecompiledScriptOf(target: T, className: String): Any = - loadCompiledKotlinClass(className) - .getConstructor(T::class.java) - .newInstance(target) - - private - fun loadCompiledKotlinClass(className: String) = - classLoaderFor(existing("build/classes/kotlin/main")) - .loadClass(className) - - private - fun withKotlinDslPlugin() = - withBuildScript(scriptWithKotlinDslPlugin()) - - private - fun scriptWithKotlinDslPlugin(): String = - """ - plugins { - `kotlin-dsl` - } - - $repositoriesBlock - """ - - private - fun compileKotlin() { - buildWithPlugin("classes") - .assertTaskExecuted(":compileKotlin") - } } diff --git a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/base/KotlinDslBasePlugin.kt b/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/base/KotlinDslBasePlugin.kt index 8d2780faf2f44..012c02f872e9f 100644 --- a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/base/KotlinDslBasePlugin.kt +++ b/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/base/KotlinDslBasePlugin.kt @@ -42,7 +42,6 @@ import org.gradle.kotlin.dsl.* class KotlinDslBasePlugin : Plugin { override fun apply(project: Project): Unit = project.run { - apply() createOptionsExtension() apply() diff --git a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslCompilerPlugins.kt b/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslCompilerPlugins.kt index 518de7e76d8e5..b445c2eda3569 100644 --- a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslCompilerPlugins.kt +++ b/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslCompilerPlugins.kt @@ -23,6 +23,7 @@ import org.gradle.api.logging.Logger import org.gradle.api.internal.DocumentationRegistry import org.gradle.api.internal.TaskInternal +import org.gradle.internal.logging.slf4j.ContextAwareTaskLogger import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -70,11 +71,12 @@ class KotlinDslCompilerPlugins : Plugin { private -fun KotlinCompile.applyExperimentalWarning(experimentalWarning: Boolean) = +fun KotlinCompile.applyExperimentalWarning(experimentalWarning: Boolean) { replaceLoggerWith( - if (experimentalWarning) KotlinCompilerWarningSubstitutingLogger(logger, project.toString(), project.experimentalWarningLink) - else KotlinCompilerWarningSilencingLogger(logger) + if (experimentalWarning) KotlinCompilerWarningSubstitutingLogger(logger as ContextAwareTaskLogger, project.toString(), project.experimentalWarningLink) + else KotlinCompilerWarningSilencingLogger(logger as ContextAwareTaskLogger) ) +} object KotlinCompilerArguments { @@ -93,10 +95,10 @@ fun KotlinCompile.replaceLoggerWith(logger: Logger) { private class KotlinCompilerWarningSubstitutingLogger( - private val delegate: Logger, + private val delegate: ContextAwareTaskLogger, private val target: String, private val link: String -) : Logger by delegate { +) : ContextAwareTaskLogger by delegate { override fun warn(message: String) { if (message.contains(KotlinCompilerArguments.samConversionForKotlinFunctions)) delegate.warn(kotlinDslPluginExperimentalWarning(target, link)) @@ -107,8 +109,8 @@ class KotlinCompilerWarningSubstitutingLogger( private class KotlinCompilerWarningSilencingLogger( - private val delegate: Logger -) : Logger by delegate { + private val delegate: ContextAwareTaskLogger +) : ContextAwareTaskLogger by delegate { override fun warn(message: String) { if (!message.contains(KotlinCompilerArguments.samConversionForKotlinFunctions)) { diff --git a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPlugin.kt b/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPlugin.kt index bc7e64dee28b2..685da9f0c952b 100644 --- a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPlugin.kt +++ b/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/dsl/KotlinDslPlugin.kt @@ -18,12 +18,15 @@ package org.gradle.kotlin.dsl.plugins.dsl import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin +import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.plugins.appliedKotlinDslPluginsVersion import org.gradle.kotlin.dsl.plugins.base.KotlinDslBasePlugin import org.gradle.kotlin.dsl.plugins.precompiled.PrecompiledScriptPlugins -import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.support.expectedKotlinDslPluginsVersion + +import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin /** @@ -41,8 +44,21 @@ class KotlinDslPlugin : Plugin { override fun apply(project: Project): Unit = project.run { + warnOnUnexpectedKotlinDslPluginVersion() + apply() apply() apply() } + + private + fun Project.warnOnUnexpectedKotlinDslPluginVersion() { + if (expectedKotlinDslPluginsVersion != appliedKotlinDslPluginsVersion) { + logger.warn( + "This version of Gradle expects version '{}' of the `kotlin-dsl` plugin but version '{}' has been applied to {}. " + + "Let Gradle control the version of `kotlin-dsl` by removing any explicit `kotlin-dsl` version constraints from your build logic.", + expectedKotlinDslPluginsVersion, appliedKotlinDslPluginsVersion, project + ) + } + } } diff --git a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPlugins.kt b/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPlugins.kt index 35c70b691ede1..9db8fd48259c1 100644 --- a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPlugins.kt +++ b/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/PrecompiledScriptPlugins.kt @@ -17,146 +17,60 @@ package org.gradle.kotlin.dsl.plugins.precompiled import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.file.SourceDirectorySet import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.TaskProvider import org.gradle.kotlin.dsl.* - -import org.gradle.kotlin.dsl.precompile.PrecompiledInitScript -import org.gradle.kotlin.dsl.precompile.PrecompiledProjectScript -import org.gradle.kotlin.dsl.precompile.PrecompiledScriptDependenciesResolver -import org.gradle.kotlin.dsl.precompile.PrecompiledSettingsScript - +import org.gradle.kotlin.dsl.provider.PrecompiledScriptPluginsSupport import org.gradle.kotlin.dsl.provider.gradleKotlinDslJarsOf - -import org.gradle.kotlin.dsl.support.ImplicitImports import org.gradle.kotlin.dsl.support.serviceOf -import org.gradle.plugin.devel.GradlePluginDevelopmentExtension -import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin - import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -/* +/** * Exposes `*.gradle.kts` scripts from regular Kotlin source-sets as binary Gradle plugins. + * + * @see PrecompiledScriptPluginsSupport */ class PrecompiledScriptPlugins : Plugin { override fun apply(project: Project): Unit = project.run { - enableScriptCompilation() - - plugins.withType { - exposeScriptsAsGradlePlugins() - } - } -} - - -private -fun Project.enableScriptCompilation() { - - dependencies { - "kotlinCompilerPluginClasspath"(gradleKotlinDslJarsOf(project)) - "kotlinCompilerPluginClasspath"(gradleApi()) - } + if (serviceOf().enableOn(Target(project))) { - tasks.named("compileKotlin") { - kotlinOptions { - freeCompilerArgs += listOf( - "-script-templates", scriptTemplates, - // Propagate implicit imports and other settings - "-Xscript-resolver-environment=${resolverEnvironment()}" - ) + dependencies { + "kotlinCompilerPluginClasspath"(gradleKotlinDslJarsOf(project)) + "kotlinCompilerPluginClasspath"(gradleApi()) + } } } -} + private + class Target(override val project: Project) : PrecompiledScriptPluginsSupport.Target { -private -val scriptTemplates by lazy { - listOf( - // treat *.settings.gradle.kts files as Settings scripts - PrecompiledSettingsScript::class.qualifiedName!!, - // treat *.init.gradle.kts files as Gradle scripts - PrecompiledInitScript::class.qualifiedName!!, - // treat *.gradle.kts files as Project scripts - PrecompiledProjectScript::class.qualifiedName!! - ).joinToString(separator = ",") -} + override val kotlinSourceDirectorySet: SourceDirectorySet + get() = project.sourceSets["main"].kotlin + override val kotlinCompileTask: TaskProvider + get() = project.tasks.named("compileKotlin") -private -fun Project.resolverEnvironment() = - (PrecompiledScriptDependenciesResolver.EnvironmentProperties.kotlinDslImplicitImports - + "=\"" + implicitImports().joinToString(separator = ":") + "\"") - - -private -fun Project.implicitImports(): List = - serviceOf().list - - -private -fun Project.exposeScriptsAsGradlePlugins() { - - val scriptSourceFiles = pluginSourceSet.allSource.matching { - it.include("**/*.gradle.kts") - } - - val scriptPlugins = - scriptSourceFiles.map(::ScriptPlugin) - - declareScriptPlugins(scriptPlugins) - - generatePluginAdaptersFor(scriptPlugins) -} - - -private -val Project.pluginSourceSet - get() = gradlePlugin.pluginSourceSet - - -private -val Project.gradlePlugin - get() = the() - - -private -fun Project.declareScriptPlugins(scriptPlugins: List) { - - configure { - for (scriptPlugin in scriptPlugins) { - plugins.create(scriptPlugin.id) { - it.id = scriptPlugin.id - it.implementationClass = scriptPlugin.implementationClass + override fun applyKotlinCompilerArgs(kotlinCompilerArgs: List) { + kotlinCompileTask { + require(this is KotlinCompile) + kotlinOptions { + freeCompilerArgs += kotlinCompilerArgs + } } } } } -private -fun Project.generatePluginAdaptersFor(scriptPlugins: List) { - - val generatedSourcesDir = layout.buildDirectory.dir("generated-sources/kotlin-dsl-plugins/kotlin") - sourceSets["main"].kotlin.srcDir(generatedSourcesDir) - - val generateScriptPluginAdapters by tasks.registering(GenerateScriptPluginAdapters::class) { - plugins = scriptPlugins - outputDirectory.set(generatedSourcesDir) - } - - tasks.named("compileKotlin") { - it.dependsOn(generateScriptPluginAdapters) - } -} - - private val Project.sourceSets get() = project.the() diff --git a/subprojects/kotlin-dsl-provider-plugins/kotlin-dsl-provider-plugins.gradle.kts b/subprojects/kotlin-dsl-provider-plugins/kotlin-dsl-provider-plugins.gradle.kts index 5a84699ec654c..6899939b59b2f 100644 --- a/subprojects/kotlin-dsl-provider-plugins/kotlin-dsl-provider-plugins.gradle.kts +++ b/subprojects/kotlin-dsl-provider-plugins/kotlin-dsl-provider-plugins.gradle.kts @@ -14,7 +14,6 @@ * limitations under the License. */ -import build.withCompileOnlyGradleApiModulesWithParameterNames import org.gradle.gradlebuild.unittestandcompile.ModuleType plugins { @@ -27,11 +26,16 @@ gradlebuildJava { moduleType = ModuleType.CORE } -withCompileOnlyGradleApiModulesWithParameterNames(":plugins") dependencies { compile(project(":distributionsDependencies")) compile(project(":kotlinDsl")) + + compileOnly(project(":plugins")) + compileOnly(project(":pluginDevelopment")) + + testImplementation(project(":kotlinDslTestFixtures")) + testImplementation(project(":plugins")) } diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/KotlinDslProviderPluginsServiceRegistry.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/KotlinDslProviderPluginsServiceRegistry.kt index 22bf956f468b6..002b9fa59aa78 100644 --- a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/KotlinDslProviderPluginsServiceRegistry.kt +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/KotlinDslProviderPluginsServiceRegistry.kt @@ -19,6 +19,8 @@ package org.gradle.kotlin.dsl.provider.plugins import org.gradle.internal.service.ServiceRegistration import org.gradle.internal.service.scopes.AbstractPluginServiceRegistry +import org.gradle.kotlin.dsl.provider.plugins.precompiled.DefaultPrecompiledScriptPluginsSupport + class KotlinDslProviderPluginsServiceRegistry : AbstractPluginServiceRegistry() { @@ -38,4 +40,8 @@ object GradleUserHomeServices { @Suppress("unused") fun createKotlinScriptBasePluginsApplicator() = DefaultKotlinScriptBasePluginsApplicator() + + @Suppress("unused") + fun createPrecompiledScriptPluginsSupport() = + DefaultPrecompiledScriptPluginsSupport() } diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/DefaultPrecompiledScriptPluginsSupport.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/DefaultPrecompiledScriptPluginsSupport.kt new file mode 100644 index 0000000000000..6e463148ace14 --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/DefaultPrecompiledScriptPluginsSupport.kt @@ -0,0 +1,404 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.kotlin.dsl.provider.plugins.precompiled + +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.Directory +import org.gradle.api.file.FileCollection +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.initialization.Settings +import org.gradle.api.invocation.Gradle +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.TaskProvider + +import org.gradle.internal.classloader.ClasspathHasher +import org.gradle.internal.classpath.ClassPath +import org.gradle.internal.classpath.DefaultClassPath +import org.gradle.internal.hash.HashCode + +import org.gradle.kotlin.dsl.* +import org.gradle.kotlin.dsl.precompile.PrecompiledInitScript +import org.gradle.kotlin.dsl.precompile.PrecompiledProjectScript +import org.gradle.kotlin.dsl.precompile.PrecompiledSettingsScript + +import org.gradle.kotlin.dsl.provider.PrecompiledScriptPluginsSupport +import org.gradle.kotlin.dsl.provider.inClassPathMode +import org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks.CompilePrecompiledScriptPluginPlugins +import org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks.ConfigurePrecompiledScriptDependenciesResolver +import org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks.ExtractPrecompiledScriptPluginPlugins +import org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks.GenerateExternalPluginSpecBuilders +import org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks.GeneratePrecompiledScriptPluginAccessors +import org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks.GenerateScriptPluginAdapters +import org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks.HashedProjectSchema + +import org.gradle.kotlin.dsl.resolver.kotlinBuildScriptModelTask +import org.gradle.kotlin.dsl.support.serviceOf + +import org.gradle.plugin.devel.GradlePluginDevelopmentExtension +import org.gradle.plugin.devel.plugins.JavaGradlePluginPlugin + +import java.util.function.Consumer + + +/** + * Exposes `*.gradle.kts` scripts from regular Kotlin source-sets as binary Gradle plugins. + * + * ## Defining the plugin target + * + * Precompiled script plugins can target one of the following Gradle model types, [Gradle], [Settings] or [Project]. + * + * The target of a given script plugin is defined via its file name suffix in the following manner: + * - the `.init.gradle.kts` file name suffix defines a [Gradle] script plugin + * - the `.settings.gradle.kts` file name suffix defines a [Settings] script plugin + * - and finally, the simpler `.gradle.kts` file name suffix defines a [Project] script plugin + * + * ## Defining the plugin id + * + * The Gradle plugin id for a precompiled script plugin is defined via its file name + * plus optional package declaration in the following manner: + * - for a script without a package declaration, the plugin id is simply the file name without the + * related plugin target suffix (see above) + * - for a script containing a package declaration, the plugin id is the declared package name dot the file name without the + * related plugin target suffix (see above) + * + * For a concrete example, take the definition of a precompiled [Project] script plugin id of + * `my.project.plugin`. Given the two rules above, there are two conventional ways to do it: + * * by naming the script `my.project.plugin.gradle.kts` and including no package declaration + * * by naming the script `plugin.gradle.kts` and including a package declaration of `my.project`: + * ```kotlin + * // plugin.gradle.kts + * package my.project + * + * // ... plugin implementation ... + * ``` + * ## Applying plugins + * Precompiled script plugins can apply plugins much in the same way as regular scripts can, using one + * of the many `apply` method overloads or, in the case of [Project] scripts, via the `plugins` block. + * + * And just as regular [Project] scripts can take advantage of + * [type-safe model accessors](https://docs.gradle.org/current/userguide/kotlin_dsl.html#type-safe-accessors) + * to model elements contributed by plugins applied via the `plugins` block, so can precompiled [Project] script plugins: + * ```kotlin + * // java7-project.gradle.kts + * + * plugins { + * java + * } + * + * java { // type-safe model accessor to the `java` extension contributed by the `java` plugin + * sourceCompatibility = JavaVersion.VERSION_1_7 + * targetCompatibility = JavaVersion.VERSION_1_7 + * } + * ``` + * ## Implementation Notes + * External plugin dependencies are declared as regular artifact dependencies but a more + * semantic preserving model could be introduced in the future. + * + * ### Type-safe accessors + * The process of generating type-safe accessors for precompiled script plugins is carried out by the + * following tasks: + * - [ExtractPrecompiledScriptPluginPlugins] - extracts the `plugins` block of every precompiled script plugin and + * saves it to a file with the same name in the output directory + * - [GenerateExternalPluginSpecBuilders] - generates plugin spec builders for the plugins in the compile classpath + * - [CompilePrecompiledScriptPluginPlugins] - compiles the extracted `plugins` blocks along with the internal + * and external plugin spec builders + * - [GeneratePrecompiledScriptPluginAccessors] - uses the compiled `plugins` block of each precompiled script plugin + * to compute its [HashedProjectSchema] and emit the corresponding type-safe accessors + */ +class DefaultPrecompiledScriptPluginsSupport : PrecompiledScriptPluginsSupport { + + override fun enableOn(target: PrecompiledScriptPluginsSupport.Target): Boolean = target.project.run { + + val scriptPlugins = collectScriptPlugins() + if (scriptPlugins.isEmpty()) { + return false + } + + enableScriptCompilationOf( + scriptPlugins, + target.kotlinCompileTask, + target.kotlinSourceDirectorySet, + target::applyKotlinCompilerArgs + ) + + plugins.withType { + exposeScriptsAsGradlePlugins( + scriptPlugins, + target.kotlinSourceDirectorySet + ) + } + return true + } + + override fun enableOn( + project: Project, + kotlinSourceDirectorySet: SourceDirectorySet, + kotlinCompileTask: TaskProvider, + kotlinCompilerArgsConsumer: Consumer> + ) { + enableOn(object : PrecompiledScriptPluginsSupport.Target { + override val project + get() = project + + override val kotlinSourceDirectorySet + get() = kotlinSourceDirectorySet + + override val kotlinCompileTask + get() = kotlinCompileTask + + override fun applyKotlinCompilerArgs(args: List) = + kotlinCompilerArgsConsumer.accept(args) + }) + } +} + + +private +fun Project.enableScriptCompilationOf( + scriptPlugins: List, + kotlinCompileTask: TaskProvider, + kotlinSourceDirectorySet: SourceDirectorySet, + applyKotlinCompilerArgs: (List) -> Unit +) { + + val extractedPluginsBlocks = buildDir("kotlin-dsl/plugins-blocks/extracted") + + val compiledPluginsBlocks = buildDir("kotlin-dsl/plugins-blocks/compiled") + + val accessorsMetadata = buildDir("kotlin-dsl/precompiled-script-plugins-metadata/accessors") + + val pluginSpecBuildersMetadata = buildDir("kotlin-dsl/precompiled-script-plugins-metadata/plugin-spec-builders") + + val compileClasspath = HashedClassPath( + { compileClasspath() }, + { hashOf(it) } + ) + + val pluginSpecBuildersPackage = provider { + "gradle.kotlin.dsl.plugins._${compileClasspath.hash}" + } + + tasks { + + val extractPrecompiledScriptPluginPlugins by registering(ExtractPrecompiledScriptPluginPlugins::class) { + plugins = scriptPlugins + outputDir.set(extractedPluginsBlocks) + } + + val (generateExternalPluginSpecBuilders, externalPluginSpecBuilders) = + codeGenerationTask( + "external-plugin-spec-builders", + "generateExternalPluginSpecBuilders", + kotlinSourceDirectorySet + ) { + hashedClassPath = compileClasspath + sourceCodeOutputDir.set(it) + metadataOutputDir.set(pluginSpecBuildersMetadata) + sharedAccessorsPackage.set(pluginSpecBuildersPackage) + } + + val compilePluginsBlocks by registering(CompilePrecompiledScriptPluginPlugins::class) { + + dependsOn(extractPrecompiledScriptPluginPlugins) + sourceDir(extractedPluginsBlocks) + + dependsOn(generateExternalPluginSpecBuilders) + sourceDir(externalPluginSpecBuilders) + + hashedClassPath = compileClasspath + outputDir.set(compiledPluginsBlocks) + sharedAccessorsPackage.set(pluginSpecBuildersPackage) + } + + val (generatePrecompiledScriptPluginAccessors, _) = + codeGenerationTask( + "accessors", + "generatePrecompiledScriptPluginAccessors", + kotlinSourceDirectorySet + ) { + dependsOn(compilePluginsBlocks) + hashedClassPath = compileClasspath + runtimeClassPathFiles = configurations["runtimeClasspath"] + sourceCodeOutputDir.set(it) + metadataOutputDir.set(accessorsMetadata) + compiledPluginsBlocksDir.set(compiledPluginsBlocks) + plugins = scriptPlugins + } + + val configurePrecompiledScriptDependenciesResolver by registering(ConfigurePrecompiledScriptDependenciesResolver::class) { + dependsOn(generatePrecompiledScriptPluginAccessors) + metadataDir.set(accessorsMetadata) + sharedAccessorsPackage.set(pluginSpecBuildersPackage) + onConfigure { resolverEnvironment -> + applyKotlinCompilerArgs( + listOf( + "-script-templates", scriptTemplates, + // Propagate implicit imports and other settings + "-Xscript-resolver-environment=$resolverEnvironment" + ) + ) + } + } + + kotlinCompileTask { + dependsOn(configurePrecompiledScriptDependenciesResolver) + } + + if (inClassPathMode()) { + registerBuildScriptModelTask( + configurePrecompiledScriptDependenciesResolver + ) + } + } +} + + +internal +class HashedClassPath( + filesProvider: () -> FileCollection, + private val classPathHasher: (ClassPath) -> HashCode +) { + + val classPathFiles by lazy(filesProvider) + + val classPath by lazy { + DefaultClassPath.of(classPathFiles.files) + } + + val hash by lazy { + classPathHasher(classPath) + } +} + + +private +fun Project.hashOf(classPath: ClassPath) = + project.serviceOf().hash(classPath) + + +private +fun Project.registerBuildScriptModelTask( + modelTask: TaskProvider +) { + rootProject.tasks.named(kotlinBuildScriptModelTask) { + it.dependsOn(modelTask) + } +} + + +private +fun Project.compileClasspath(): FileCollection = + sourceSets["main"].compileClasspath + + +private +val scriptTemplates by lazy { + listOf( + // treat *.settings.gradle.kts files as Settings scripts + PrecompiledSettingsScript::class.qualifiedName!!, + // treat *.init.gradle.kts files as Gradle scripts + PrecompiledInitScript::class.qualifiedName!!, + // treat *.gradle.kts files as Project scripts + PrecompiledProjectScript::class.qualifiedName!! + ).joinToString(separator = ",") +} + + +private +fun Project.exposeScriptsAsGradlePlugins(scriptPlugins: List, kotlinSourceDirectorySet: SourceDirectorySet) { + + declareScriptPlugins(scriptPlugins) + + generatePluginAdaptersFor(scriptPlugins, kotlinSourceDirectorySet) +} + + +private +fun Project.collectScriptPlugins(): List = + mutableListOf().apply { + pluginSourceSet.allSource.matching { + it.include("**/*.gradle.kts") + }.visit { + if (!it.isDirectory) { + // TODO: preserve it.relativePath in PrecompiledScriptPlugin + add(PrecompiledScriptPlugin(it.file)) + } + } + } + + +private +val Project.pluginSourceSet + get() = gradlePlugin.pluginSourceSet + + +private +val Project.gradlePlugin + get() = the() + + +private +fun Project.declareScriptPlugins(scriptPlugins: List) { + + configure { + for (scriptPlugin in scriptPlugins) { + plugins.create(scriptPlugin.id) { + it.id = scriptPlugin.id + it.implementationClass = scriptPlugin.implementationClass + } + } + } +} + + +private +fun Project.generatePluginAdaptersFor(scriptPlugins: List, kotlinSourceDirectorySet: SourceDirectorySet) { + + codeGenerationTask( + "plugins", + "generateScriptPluginAdapters", + kotlinSourceDirectorySet + ) { + plugins = scriptPlugins + outputDirectory.set(it) + } +} + + +private +inline fun Project.codeGenerationTask( + purpose: String, + taskName: String, + kotlinSourceDirectorySet: SourceDirectorySet, + noinline configure: T.(Provider) -> Unit +) = buildDir("generated-sources/kotlin-dsl-$purpose/kotlin").let { outputDir -> + val task = tasks.register(taskName, T::class.java) { + it.configure(outputDir) + } + kotlinSourceDirectorySet.srcDir(files(outputDir).builtBy(task)) + task to outputDir +} + + +private +fun Project.buildDir(path: String) = layout.buildDirectory.dir(path) + + +private +val Project.sourceSets + get() = project.the() diff --git a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/ScriptPlugin.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/PrecompiledScriptPlugin.kt similarity index 89% rename from subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/ScriptPlugin.kt rename to subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/PrecompiledScriptPlugin.kt index 5669bca8c28b2..75414e069cd69 100644 --- a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/ScriptPlugin.kt +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/PrecompiledScriptPlugin.kt @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.gradle.kotlin.dsl.plugins.precompiled +package org.gradle.kotlin.dsl.provider.plugins.precompiled import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.initialization.Settings import org.gradle.api.invocation.Gradle +import org.gradle.internal.hash.Hashing import org.gradle.kotlin.dsl.support.KotlinScriptType import org.gradle.kotlin.dsl.support.KotlinScriptTypeMatch @@ -34,9 +35,10 @@ import java.io.File internal -data class ScriptPlugin(internal val scriptFile: File) { +data class PrecompiledScriptPlugin(internal val scriptFile: File) { - val scriptFileName: String = scriptFile.name + val scriptFileName: String + get() = scriptFile.name /** * Gradle plugin id inferred from the script file name and package declaration (if any). @@ -75,7 +77,6 @@ data class ScriptPlugin(internal val scriptFile: File) { } } - private val scriptType get() = scriptTypeMatch.scriptType @@ -99,12 +100,23 @@ data class ScriptPlugin(internal val scriptFile: File) { packageNameOf(scriptFile) } + val hashString by lazy { + Hashing.hashString(scriptText).toString() + } + + val scriptText: String + get() = scriptFile.readText() + private fun packagePrefixed(id: String) = packageName?.let { "$it.$id" } ?: id } +internal +fun scriptPluginFilesOf(list: List) = list.map { it.scriptFile }.toSet() + + private fun packageNameOf(file: File): String? = packageNameOf(normaliseLineSeparators(file.readText())) diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ClassPathSensitiveCodeGenerationTask.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ClassPathSensitiveCodeGenerationTask.kt new file mode 100644 index 0000000000000..f22a762f77a0c --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ClassPathSensitiveCodeGenerationTask.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.OutputDirectory + + +abstract class ClassPathSensitiveCodeGenerationTask : ClassPathSensitiveTask() { + + @get:OutputDirectory + abstract val sourceCodeOutputDir: DirectoryProperty +} diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ClassPathSensitiveTask.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ClassPathSensitiveTask.kt new file mode 100644 index 0000000000000..c538482deeba5 --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ClassPathSensitiveTask.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.file.FileCollection +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Internal + +import org.gradle.internal.classpath.ClassPath +import org.gradle.internal.hash.HashCode + +import org.gradle.kotlin.dsl.provider.plugins.precompiled.HashedClassPath + + +abstract class ClassPathSensitiveTask : DefaultTask() { + + @get:Internal + internal + lateinit var hashedClassPath: HashedClassPath + + @get:Classpath + val classPathFiles: FileCollection + get() = hashedClassPath.classPathFiles + + @get:Internal + protected + val classPath: ClassPath + get() = hashedClassPath.classPath + + @get:Internal + protected + val classPathHash: HashCode + get() = hashedClassPath.hash +} diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/CompilePrecompiledScriptPluginPlugins.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/CompilePrecompiledScriptPluginPlugins.kt new file mode 100644 index 0000000000000..772bb54cd9adb --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/CompilePrecompiledScriptPluginPlugins.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.api.file.Directory +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction + +import org.gradle.kotlin.dsl.execution.scriptDefinitionFromTemplate + +import org.gradle.kotlin.dsl.support.KotlinPluginsBlock +import org.gradle.kotlin.dsl.support.compileKotlinScriptModuleTo + + +@CacheableTask +abstract class CompilePrecompiledScriptPluginPlugins : ClassPathSensitiveTask(), SharedAccessorsPackageAware { + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + val sourceFiles: SourceDirectorySet = project.objects.sourceDirectorySet( + "precompiled-script-plugin-plugins", + "Precompiled script plugin plugins" + ) + + fun sourceDir(dir: Provider) { + sourceFiles.srcDir(dir) + } + + @TaskAction + fun compile() { + outputDir.withOutputDirectory { outputDir -> + val scriptFiles = sourceFiles.map { it.path } + if (scriptFiles.isNotEmpty()) + compileKotlinScriptModuleTo( + outputDir, + sourceFiles.name, + scriptFiles, + scriptDefinitionFromTemplate( + KotlinPluginsBlock::class, + implicitImportsForPrecompiledScriptPlugins() + ), + classPathFiles, + logger, + { it } // TODO: translate paths + ) + } + } +} diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ConfigurePrecompiledScriptDependenciesResolver.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ConfigurePrecompiledScriptDependenciesResolver.kt new file mode 100644 index 0000000000000..215d4efc1fc44 --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ConfigurePrecompiledScriptDependenciesResolver.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction + +import org.gradle.kotlin.dsl.precompile.PrecompiledScriptDependenciesResolver.EnvironmentProperties.kotlinDslImplicitImports +import org.gradle.kotlin.dsl.support.ImplicitImports +import org.gradle.kotlin.dsl.support.serviceOf + + +abstract class ConfigurePrecompiledScriptDependenciesResolver : DefaultTask(), SharedAccessorsPackageAware { + + @get:Internal + abstract val metadataDir: DirectoryProperty + + private + lateinit var onConfigure: (String) -> Unit + + fun onConfigure(action: (String) -> Unit) { + onConfigure = action + } + + @TaskAction + fun configureImports() { + + val precompiledScriptPluginImports = precompiledScriptPluginImports() + + val resolverEnvironment = resolverEnvironmentStringFor( + listOf( + kotlinDslImplicitImports to implicitImportsForPrecompiledScriptPlugins() + ) + precompiledScriptPluginImports + ) + + onConfigure(resolverEnvironment) + } + + private + fun precompiledScriptPluginImports(): List>> = + metadataDirFile().run { + require(isDirectory) + listFiles().map { + it.name to it.readLines() + } + } + + private + fun metadataDirFile() = metadataDir.get().asFile + + private + fun resolverEnvironmentStringFor(properties: Iterable>>): String = + properties.joinToString(separator = ",") { (key, values) -> + "$key=\"${values.joinToString(":")}\"" + } +} + + +internal +fun Project.implicitImports() = serviceOf().list diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/DirectoryPropertyExtensions.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/DirectoryPropertyExtensions.kt new file mode 100644 index 0000000000000..635095b0a6cfd --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/DirectoryPropertyExtensions.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.api.file.DirectoryProperty + +import java.io.File + + +internal +inline fun DirectoryProperty.withOutputDirectory(action: (File) -> T): T = + asFile.get().let { outputDir -> + recreate(outputDir) + action(outputDir) + } + + +internal +fun recreate(outputDir: File) { + outputDir.deleteRecursively() + outputDir.mkdirs() +} diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ExtractPrecompiledScriptPluginPlugins.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ExtractPrecompiledScriptPluginPlugins.kt new file mode 100644 index 0000000000000..0e8301364e92f --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ExtractPrecompiledScriptPluginPlugins.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction + +import org.gradle.kotlin.dsl.execution.Program +import org.gradle.kotlin.dsl.execution.ProgramKind +import org.gradle.kotlin.dsl.execution.ProgramParser +import org.gradle.kotlin.dsl.execution.ProgramSource +import org.gradle.kotlin.dsl.execution.ProgramTarget + +import org.gradle.kotlin.dsl.provider.plugins.precompiled.PrecompiledScriptPlugin +import org.gradle.kotlin.dsl.provider.plugins.precompiled.scriptPluginFilesOf + +import org.gradle.kotlin.dsl.support.KotlinScriptType + +import java.io.File + + +/** + * Extracts the `plugins` block of each precompiled [Project] script plugin + * and writes it to a file with the same name under [outputDir]. + */ +@CacheableTask +abstract class ExtractPrecompiledScriptPluginPlugins : DefaultTask() { + + @get:OutputDirectory + abstract val outputDir: DirectoryProperty + + @get:Internal + internal + lateinit var plugins: List + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + @Suppress("unused") + internal + val scriptFiles: Set + get() = scriptPluginFilesOf(plugins) + + @TaskAction + fun extract() { + outputDir.withOutputDirectory { + extractPrecompiledScriptPluginPluginsTo(it, plugins) + } + } +} + + +internal +fun extractPrecompiledScriptPluginPluginsTo(outputDir: File, scriptPlugins: List) { + for (scriptPlugin in scriptPlugins) { + pluginsBlockOf(scriptPlugin)?.let { + writePluginsBlockTo(outputDir, scriptPlugin, it) + } + } +} + + +private +fun pluginsBlockOf(scriptPlugin: PrecompiledScriptPlugin): Program.Plugins? = + when (scriptPlugin.scriptType) { + KotlinScriptType.PROJECT -> pluginsBlockOf(parse(scriptPlugin)) + else -> null + } + + +private +fun pluginsBlockOf(program: Program): Program.Plugins? = + when (program) { + is Program.Plugins -> program + is Program.Stage1Sequence -> program.plugins + is Program.Staged -> pluginsBlockOf(program.stage1) + else -> null + } + + +private +fun parse(scriptPlugin: PrecompiledScriptPlugin): Program = ProgramParser.parse( + ProgramSource(scriptPlugin.scriptFileName, scriptPlugin.scriptText), + ProgramKind.TopLevel, + ProgramTarget.Project +) + + +private +fun writePluginsBlockTo( + outputDir: File, + scriptPlugin: PrecompiledScriptPlugin, + program: Program.Plugins +) { + outputFileFor(scriptPlugin, outputDir).writeText( + packageDeclarationOf(scriptPlugin) + lineNumberPreservingTextOf(program) + ) +} + + +private +fun outputFileFor(scriptPlugin: PrecompiledScriptPlugin, outputDir: File) = + packageDirFor(scriptPlugin, outputDir).resolve(scriptPlugin.scriptFileName) + + +private +fun packageDirFor(scriptPlugin: PrecompiledScriptPlugin, outputDir: File): File = + scriptPlugin.packageName?.run { + outputDir.resolve(replace('.', '/')).apply { + mkdirs() + } + } ?: outputDir + + +private +fun packageDeclarationOf(scriptPlugin: PrecompiledScriptPlugin): String = + scriptPlugin.packageName?.let { + "package $it; " + } ?: "" + + +private +fun lineNumberPreservingTextOf(program: Program.Plugins): String = program.fragment.run { + source.map { it.subText(0..range.endInclusive).preserve(range) } +}.text diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GenerateExternalPluginSpecBuilders.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GenerateExternalPluginSpecBuilders.kt new file mode 100644 index 0000000000000..f30cfc87bcdb8 --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GenerateExternalPluginSpecBuilders.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction + +import org.gradle.kotlin.dsl.accessors.writeSourceCodeForPluginSpecBuildersFor + +import java.io.File + + +@CacheableTask +abstract class GenerateExternalPluginSpecBuilders : ClassPathSensitiveCodeGenerationTask(), SharedAccessorsPackageAware { + + @get:OutputDirectory + abstract val metadataOutputDir: DirectoryProperty + + @TaskAction + @Suppress("unused") + internal + fun generate() { + sourceCodeOutputDir.withOutputDirectory { outputDir -> + val packageDir = createPackageDirIn(outputDir) + val outputFile = packageDir.resolve("PluginSpecBuilders.kt") + writeSourceCodeForPluginSpecBuildersFor( + classPath, + outputFile, + packageName() + ) + } + metadataOutputDir.withOutputDirectory { outputDir -> + outputDir.resolve("implicit-imports").writeText( + packageName() + ".*" + ) + } + } + + private + fun createPackageDirIn(outputDir: File) = outputDir.resolve(packagePath()).apply { mkdirs() } + + private + fun packagePath() = packageName().split('.').joinToString("/") + + private + fun packageName() = sharedAccessorsPackage.get() +} diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GeneratePrecompiledScriptPluginAccessors.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GeneratePrecompiledScriptPluginAccessors.kt new file mode 100644 index 0000000000000..5f3fa2c0b831b --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GeneratePrecompiledScriptPluginAccessors.kt @@ -0,0 +1,411 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.api.Project +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection +import org.gradle.api.internal.artifacts.dependencies.DefaultSelfResolvingDependency +import org.gradle.api.internal.file.FileCollectionFactory +import org.gradle.api.internal.file.FileCollectionInternal +import org.gradle.api.internal.initialization.ScriptHandlerInternal +import org.gradle.api.internal.project.ProjectInternal +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.InputDirectory +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.TaskAction +import org.gradle.groovy.scripts.TextResourceScriptSource +import org.gradle.initialization.ClassLoaderScopeRegistry +import org.gradle.internal.classpath.DefaultClassPath +import org.gradle.internal.concurrent.CompositeStoppable.stoppable +import org.gradle.internal.hash.HashCode +import org.gradle.internal.resource.BasicTextResourceLoader + +import org.gradle.kotlin.dsl.accessors.AccessorFormats +import org.gradle.kotlin.dsl.accessors.TypedProjectSchema +import org.gradle.kotlin.dsl.accessors.buildAccessorsFor +import org.gradle.kotlin.dsl.accessors.hashCodeFor +import org.gradle.kotlin.dsl.accessors.schemaFor + +import org.gradle.kotlin.dsl.concurrent.IO +import org.gradle.kotlin.dsl.concurrent.withAsynchronousIO +import org.gradle.kotlin.dsl.concurrent.writeFile + +import org.gradle.kotlin.dsl.precompile.PrecompiledScriptDependenciesResolver + +import org.gradle.kotlin.dsl.provider.plugins.precompiled.PrecompiledScriptPlugin +import org.gradle.kotlin.dsl.provider.plugins.precompiled.scriptPluginFilesOf + +import org.gradle.kotlin.dsl.support.KotlinScriptType +import org.gradle.kotlin.dsl.support.serviceOf + +import org.gradle.plugin.management.internal.DefaultPluginRequests +import org.gradle.plugin.management.internal.PluginRequests + +import org.gradle.plugin.use.PluginDependenciesSpec +import org.gradle.plugin.use.internal.PluginRequestApplicator +import org.gradle.plugin.use.internal.PluginRequestCollector + +import org.gradle.testfixtures.ProjectBuilder + +import java.io.File +import java.net.URLClassLoader +import java.nio.file.Files + + +@CacheableTask +abstract class GeneratePrecompiledScriptPluginAccessors : ClassPathSensitiveCodeGenerationTask() { + + @get:Classpath + lateinit var runtimeClassPathFiles: FileCollection + + @get:OutputDirectory + abstract val metadataOutputDir: DirectoryProperty + + @get:InputDirectory + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val compiledPluginsBlocksDir: DirectoryProperty + + @get:Internal + internal + lateinit var plugins: List + + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + @Suppress("unused") + internal + val scriptFiles: Set + get() = scriptPluginFilesOf(plugins) + + /** + * ## Computation and sharing of type-safe accessors + * 1. Group precompiled script plugins by the list of plugins applied in their `plugins` block. + * 2. For each group, compute the project schema implied by the list of plugins. + * 3. Re-group precompiled script plugins by project schema. + * 4. For each group, emit the type-safe accessors implied by the schema to a package named after the schema + * hash code. + * 5. For each group, for each script plugin in the group, write the generated package name to a file named + * after the contents of the script plugin file. This is so the file can be easily found by + * [PrecompiledScriptDependenciesResolver]. + */ + @TaskAction + fun generate() { + + recreateTaskDirectories() + + val projectPlugins = selectProjectPlugins() + if (projectPlugins.isNotEmpty()) { + withAsynchronousIO(project) { + generateTypeSafeAccessorsFor(projectPlugins) + } + } + } + + private + fun recreateTaskDirectories() { + recreate(temporaryDir) + recreate(sourceCodeOutputDir.get().asFile) + recreate(metadataOutputDir.get().asFile) + } + + private + fun IO.generateTypeSafeAccessorsFor(projectPlugins: List) { + resolvePluginGraphOf(projectPlugins) + .groupBy( + { it.plugins }, + { it.scriptPlugin } + ).let { + projectSchemaImpliedByPluginGroups(it) + }.forEach { (projectSchema, scriptPlugins) -> + writeTypeSafeAccessorsFor(projectSchema) + for (scriptPlugin in scriptPlugins) { + writeContentAddressableImplicitImportFor( + projectSchema.packageName, + scriptPlugin + ) + } + } + } + + private + fun resolvePluginGraphOf(projectPlugins: List): Sequence { + + val scriptPluginsById = scriptPluginPluginsFor(projectPlugins).associateBy { + it.scriptPlugin.id + } + + val pluginGraph = plugins.associate { + it.id to pluginsAppliedBy(it, scriptPluginsById) + } + + return reduceGraph(pluginGraph).asSequence().mapNotNull { (id, plugins) -> + scriptPluginsById[id]?.copy(plugins = plugins.toList()) + } + } + + private + fun pluginsAppliedBy(scriptPlugin: PrecompiledScriptPlugin, scriptPluginsById: Map) = + scriptPluginsById[scriptPlugin.id]?.plugins ?: emptyList() + + private + fun scriptPluginPluginsFor(projectPlugins: List) = sequence { + val loader = createPluginsClassLoader() + try { + for (plugin in projectPlugins) { + loader.scriptPluginPluginsFor(plugin)?.let { + yield(it) + } + } + } finally { + stoppable(loader).stop() + } + } + + private + fun ClassLoader.scriptPluginPluginsFor(plugin: PrecompiledScriptPlugin): ScriptPluginPlugins? { + + // The compiled script class won't be present for precompiled script plugins + // which don't include a `plugins` block + if (getResource(compiledScriptClassFile(plugin)) == null) { + return null + } + + return ScriptPluginPlugins( + plugin, + collectPluginRequestsOf(plugin).map { + // TODO:kotlin-dsl validate plugin request version, apply false, etc + it.id.id + } + ) + } + + private + fun ClassLoader.collectPluginRequestsOf(plugin: PrecompiledScriptPlugin): PluginRequests = + pluginRequestCollectorFor(plugin).run { + + loadClass(plugin.compiledScriptTypeName) + .getConstructor(PluginDependenciesSpec::class.java) + .newInstance(createSpec(1)) + + pluginRequests + } + + + private + fun pluginRequestCollectorFor(plugin: PrecompiledScriptPlugin) = + PluginRequestCollector(scriptSourceFor(plugin)) + + private + fun scriptSourceFor(plugin: PrecompiledScriptPlugin) = + TextResourceScriptSource( + BasicTextResourceLoader().loadFile("Precompiled script plugin", plugin.scriptFile) + ) + + private + fun compiledScriptClassFile(plugin: PrecompiledScriptPlugin) = + plugin.compiledScriptTypeName.replace('.', '/') + ".class" + + private + fun selectProjectPlugins() = plugins.filter { it.scriptType == KotlinScriptType.PROJECT } + + private + fun createPluginsClassLoader(): ClassLoader = + URLClassLoader( + compiledPluginsClassPath().asURLArray, + classLoaderScopeRegistry().coreAndPluginsScope.localClassLoader + ) + + private + fun classLoaderScopeRegistry() = project.serviceOf() + + private + fun compiledPluginsClassPath() = + DefaultClassPath.of(compiledPluginsBlocksDir.get().asFile) + classPath + + private + fun projectSchemaImpliedByPluginGroups( + pluginGroupsPerRequests: Map, List> + ): Map> { + + val schemaBuilder = SyntheticProjectSchemaBuilder( + gradleUserHomeDir = project.gradle.gradleUserHomeDir, + rootProjectDir = uniqueTempDirectory(), + rootProjectClassPath = (classPathFiles + runtimeClassPathFiles).files + ) + return pluginGroupsPerRequests.flatMap { (uniquePluginRequests, scriptPlugins) -> + try { + val schema = schemaBuilder.schemaFor(pluginRequestsFor(uniquePluginRequests, scriptPlugins.first())) + val hashedSchema = HashedProjectSchema(schema) + scriptPlugins.map { hashedSchema to it } + } catch (error: Throwable) { + reportProjectSchemaError(scriptPlugins, error) + emptyList>() + } + }.groupBy( + { (schema, _) -> schema }, + { (_, plugin) -> plugin } + ) + } + + private + fun uniqueTempDirectory() = Files.createTempDirectory(temporaryDir.toPath(), "project-").toFile() + + private + fun pluginRequestsFor(pluginIds: List, plugin: PrecompiledScriptPlugin): PluginRequests = + pluginRequestCollectorFor(plugin).run { + createSpec(1).apply { + pluginIds.forEach { + id(it) + } + } + pluginRequests + } + + private + fun reportProjectSchemaError(plugins: List, error: Throwable) { + logger.warn( + plugins.joinToString( + prefix = "Failed to generate type-safe Gradle model accessors for the following precompiled script plugins:\n", + separator = "\n", + postfix = "\n" + ) { " - " + projectRelativePathOf(it) }, + error + ) + } + + private + fun projectRelativePathOf(scriptPlugin: PrecompiledScriptPlugin) = + project.relativePath(scriptPlugin.scriptFile) + + private + fun IO.writeTypeSafeAccessorsFor(hashedSchema: HashedProjectSchema) { + buildAccessorsFor( + hashedSchema.schema, + classPath, + sourceCodeOutputDir.get().asFile, + null, + hashedSchema.packageName, + AccessorFormats.internal + ) + } + + private + fun IO.writeContentAddressableImplicitImportFor(packageName: String, scriptPlugin: PrecompiledScriptPlugin) { + writeFile(implicitImportFileFor(scriptPlugin), "$packageName.*".toByteArray()) + } + + private + fun implicitImportFileFor(scriptPlugin: PrecompiledScriptPlugin): File = + metadataOutputDir.get().asFile.resolve(scriptPlugin.hashString) +} + + +internal +class SyntheticProjectSchemaBuilder( + gradleUserHomeDir: File, + rootProjectDir: File, + rootProjectClassPath: Collection +) { + + private + val rootProject = buildRootProject(gradleUserHomeDir, rootProjectDir, rootProjectClassPath) + + fun schemaFor(plugins: PluginRequests): TypedProjectSchema = + schemaFor(childProjectWith(plugins)) + + private + fun childProjectWith(pluginRequests: PluginRequests): Project { + + val project = ProjectBuilder.builder() + .withParent(rootProject) + .withProjectDir(rootProject.projectDir.resolve("schema")) + .build() + + applyPluginsTo(project, pluginRequests) + + return project + } + + private + fun buildRootProject( + gradleUserHomeDir: File, + projectDir: File, + rootProjectClassPath: Collection + ): Project { + + val project = ProjectBuilder.builder() + .withGradleUserHomeDir(gradleUserHomeDir) + .withProjectDir(projectDir) + .build() + + addScriptClassPathDependencyTo(project, rootProjectClassPath) + + applyPluginsTo(project, DefaultPluginRequests.EMPTY) + + return project + } + + private + fun addScriptClassPathDependencyTo(project: Project, rootProjectClassPath: Collection) { + val scriptHandler = project.buildscript as ScriptHandlerInternal + scriptHandler.addScriptClassPathDependency( + DefaultSelfResolvingDependency( + project + .serviceOf() + .fixed("precompiled-script-plugins-accessors-classpath", rootProjectClassPath) as FileCollectionInternal + ) + ) + } + + private + fun applyPluginsTo(project: Project, pluginRequests: PluginRequests) { + val targetProjectScope = (project as ProjectInternal).classLoaderScope + project.serviceOf().applyPlugins( + pluginRequests, + project.buildscript as ScriptHandlerInternal, + project.pluginManager, + targetProjectScope + ) + } +} + + +internal +data class HashedProjectSchema( + val schema: TypedProjectSchema, + val hash: HashCode = hashCodeFor(schema) +) { + val packageName by lazy { + "gradle.kotlin.dsl.accessors._$hash" + } + + override fun hashCode(): Int = hash.hashCode() + + override fun equals(other: Any?): Boolean = other is HashedProjectSchema && hash == other.hash +} + + +private +data class ScriptPluginPlugins( + val scriptPlugin: PrecompiledScriptPlugin, + val plugins: List +) diff --git a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/GenerateScriptPluginAdapters.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GenerateScriptPluginAdapters.kt similarity index 81% rename from subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/GenerateScriptPluginAdapters.kt rename to subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GenerateScriptPluginAdapters.kt index 1d6c1bda18e87..e9fa2702f5ecc 100644 --- a/subprojects/kotlin-dsl-plugins/src/main/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/GenerateScriptPluginAdapters.kt +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/GenerateScriptPluginAdapters.kt @@ -14,46 +14,48 @@ * limitations under the License. */ -package org.gradle.kotlin.dsl.plugins.precompiled +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity -import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.provider.plugins.precompiled.PrecompiledScriptPlugin +import org.gradle.kotlin.dsl.provider.plugins.precompiled.scriptPluginFilesOf + import org.gradle.kotlin.dsl.support.normaliseLineSeparators import java.io.File @CacheableTask -open class GenerateScriptPluginAdapters : DefaultTask() { +abstract class GenerateScriptPluginAdapters : DefaultTask() { + + @get:OutputDirectory + abstract val outputDirectory: DirectoryProperty @get:Internal internal - lateinit var plugins: List - - @get:OutputDirectory - var outputDirectory = project.objects.directoryProperty() + lateinit var plugins: List @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) @Suppress("unused") internal val scriptFiles: Set - get() = plugins.map { it.scriptFile }.toSet() + get() = scriptPluginFilesOf(plugins) @TaskAction @Suppress("unused") internal fun generate() = - outputDirectory.asFile.get().let { outputDir -> - outputDir.deleteRecursively() - outputDir.mkdirs() + outputDirectory.withOutputDirectory { outputDir -> for (scriptPlugin in plugins) { scriptPlugin.writeScriptPluginAdapterTo(outputDir) } @@ -62,7 +64,7 @@ open class GenerateScriptPluginAdapters : DefaultTask() { internal -fun ScriptPlugin.writeScriptPluginAdapterTo(outputDir: File) { +fun PrecompiledScriptPlugin.writeScriptPluginAdapterTo(outputDir: File) { val (packageDir, packageDeclaration) = packageName?.let { packageName -> diff --git a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskOutputCachingBuildCacheKey.java b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/SharedAccessorsPackageAware.kt similarity index 53% rename from subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskOutputCachingBuildCacheKey.java rename to subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/SharedAccessorsPackageAware.kt index 8649557df60a4..df86228306ce4 100644 --- a/subprojects/core/src/main/java/org/gradle/caching/internal/tasks/TaskOutputCachingBuildCacheKey.java +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/SharedAccessorsPackageAware.kt @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,20 @@ * limitations under the License. */ -package org.gradle.caching.internal.tasks; +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks -import org.gradle.caching.BuildCacheKey; -import org.gradle.util.Path; +import org.gradle.api.Task +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input -import javax.annotation.Nullable; -public interface TaskOutputCachingBuildCacheKey extends BuildCacheKey { - Path getTaskPath(); +interface SharedAccessorsPackageAware { - BuildCacheKeyInputs getInputs(); + @get:Input + val sharedAccessorsPackage: Property +} - @Nullable - byte[] getHashCodeBytes(); - /** - * Whether this key can be used to retrieve or store task output entries. - */ - boolean isValid(); -} +internal +fun T.implicitImportsForPrecompiledScriptPlugins() where T : Task, T : SharedAccessorsPackageAware = + project.implicitImports() + "${sharedAccessorsPackage.get()}.*" diff --git a/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/reduceGraph.kt b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/reduceGraph.kt new file mode 100644 index 0000000000000..2b18931848269 --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/main/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/reduceGraph.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + + +/** + * Reduces the given [graph] so that it only includes external node references, + * e.g.: + * ``` + * reduce({a: [b, c], b: [e1], c: [d], d: [e2]}) => {a: [e1, e2], b: [e1], c: [e2], d: [e2]} + * ``` + */ +internal +fun reduceGraph(graph: Map>): Map> { + + val result = mutableMapOf>() + + val reducing = hashSetOf() + + fun reduce(node: V, refs: Iterable): Set { + + result[node]?.let { + return it + } + + val externalNodes = mutableSetOf() + result[node] = externalNodes + + reducing.add(node) + for (ref in refs) { + if (ref in reducing) { + continue + } + graph[ref]?.let { + externalNodes.addAll(reduce(ref, it)) + } ?: externalNodes.add(ref) + } + reducing.remove(node) + + return externalNodes + } + + for ((node, refs) in graph) { + reduce(node, refs) + } + + return result +} diff --git a/subprojects/kotlin-dsl-plugins/src/test/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/ScriptPluginTest.kt b/subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/PrecompiledScriptPluginTest.kt similarity index 69% rename from subprojects/kotlin-dsl-plugins/src/test/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/ScriptPluginTest.kt rename to subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/PrecompiledScriptPluginTest.kt index d54d4880bf76c..a7861f2e3ad43 100644 --- a/subprojects/kotlin-dsl-plugins/src/test/kotlin/org/gradle/kotlin/dsl/plugins/precompiled/ScriptPluginTest.kt +++ b/subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/PrecompiledScriptPluginTest.kt @@ -1,7 +1,9 @@ -package org.gradle.kotlin.dsl.plugins.precompiled +package org.gradle.kotlin.dsl.provider.plugins.precompiled import org.gradle.kotlin.dsl.fixtures.TestWithTempFiles +import org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks.writeScriptPluginAdapterTo + import org.hamcrest.CoreMatchers.equalTo import org.hamcrest.CoreMatchers.startsWith import org.hamcrest.MatcherAssert.assertThat @@ -11,32 +13,31 @@ import org.junit.Test import java.io.File -class ScriptPluginTest : TestWithTempFiles() { +class PrecompiledScriptPluginTest : TestWithTempFiles() { @Test fun `plugin id is derived from script file name`() { - val script = - newFile("my-script.gradle.kts") - assertThat( - ScriptPlugin(script).id, - equalTo("my-script")) + scriptPlugin("my-script.gradle.kts").id, + equalTo("my-script") + ) } @Test fun `plugin id is prefixed by package name if present`() { - val script = - newFile("my-script.gradle.kts", """ - - package org.acme + assertThat( + scriptPlugin( + "my-script.gradle.kts", + """ - """) + package org.acme - assertThat( - ScriptPlugin(script).id, - equalTo("org.acme.my-script")) + """ + ).id, + equalTo("org.acme.my-script") + ) } @Test @@ -57,23 +58,22 @@ class ScriptPluginTest : TestWithTempFiles() { private fun implementationClassForScriptNamed(fileName: String) = - ScriptPlugin(newFile(fileName)).implementationClass + scriptPlugin(fileName).implementationClass @Test fun `plugin adapter is written to package sub-dir and starts with correct package declaration`() { - val script = - newFile("my-script.gradle.kts", """ - - package org.acme - - """) - val outputDir = root.resolve("output") - ScriptPlugin(script) - .writeScriptPluginAdapterTo(outputDir) + scriptPlugin( + "my-script.gradle.kts", + """ + + package org.acme + + """ + ).writeScriptPluginAdapterTo(outputDir) val expectedFile = outputDir.resolve("org/acme/MyScriptPlugin.kt") @@ -86,13 +86,10 @@ class ScriptPluginTest : TestWithTempFiles() { @Test fun `given no package declaration, plugin adapter is written directly to output dir`() { - val script = - newFile("my-script.gradle.kts") - val outputDir = root.resolve("output").apply { mkdir() } - ScriptPlugin(script) + scriptPlugin("my-script.gradle.kts") .writeScriptPluginAdapterTo(outputDir) val expectedFile = @@ -113,14 +110,18 @@ class ScriptPluginTest : TestWithTempFiles() { @Test fun `can extract package name from script with Windows line endings`() { - val script = - newFile("my-script.gradle.kts", "/*\r\n */\r\npackage org.acme\r\n") - assertThat( - ScriptPlugin(script).packageName, - equalTo("org.acme")) + scriptPlugin( + "my-script.gradle.kts", + "/*\r\n */\r\npackage org.acme\r\n" + ).packageName, + equalTo("org.acme") + ) } + private + fun scriptPlugin(fileName: String, text: String = "") = PrecompiledScriptPlugin(newFile(fileName, text)) + private fun firstNonBlankLineOf(expectedFile: File) = expectedFile.bufferedReader().useLines { diff --git a/subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ExtractPrecompiledScriptPluginPluginsTest.kt b/subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ExtractPrecompiledScriptPluginPluginsTest.kt new file mode 100644 index 0000000000000..2e52446201fcf --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ExtractPrecompiledScriptPluginPluginsTest.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.gradle.kotlin.dsl.fixtures.TestWithTempFiles +import org.gradle.kotlin.dsl.fixtures.equalToMultiLineString + +import org.gradle.kotlin.dsl.provider.plugins.precompiled.PrecompiledScriptPlugin + +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.TypeSafeMatcher + +import org.junit.Test + +import java.io.File + + +class ExtractPrecompiledScriptPluginPluginsTest : TestWithTempFiles() { + + private + val outputDir by lazy { + newFolder("plugins") + } + + @Test + fun `can extract plugins block from script with only plugins`() { + + extractPluginsFrom( + scriptPlugin( + "plugins-only.gradle.kts", + """ + // this comment will be removed + plugins { + java + } + // and so will the rest of the script + """ + ) + ) + + assertThat( + outputFile("plugins-only.gradle.kts").readText(), + equalToMultiLineString(""" + ${"// this comment will be removed".replacedBySpaces()} + plugins { + java + }""" + ) + ) + } + + @Test + fun `can extract plugins block from script with a buildscript block`() { + + extractPluginsFrom( + // the `buildscript` block is not really valid in precompiled script plugins (causes a runtime error) + // but still worth testing here + scriptPlugin("buildscript-and-plugins.gradle.kts", """ + buildscript {} + plugins { java } + """) + ) + + assertThat( + outputFile("buildscript-and-plugins.gradle.kts").readText(), + equalToMultiLineString(""" + ${"buildscript {}".replacedBySpaces()} + plugins { java }""" + ) + ) + } + + @Test + fun `ignores scripts with a nonexistent or empty plugins block`() { + + extractPluginsFrom( + + scriptPlugin("no-plugins.gradle.kts", """ + buildscript {} + """), + + scriptPlugin("empty-plugins.gradle.kts", """ + plugins {} + """) + ) + + assertThat( + outputFile("no-plugins.gradle.kts"), + doesNotExist() + ) + + assertThat( + outputFile("empty-plugins.gradle.kts"), + doesNotExist() + ) + } + + private + fun extractPluginsFrom(vararg scriptPlugins: PrecompiledScriptPlugin) { + extractPrecompiledScriptPluginPluginsTo( + outputDir, + scriptPlugins.asList() + ) + } + + private + fun outputFile(fileName: String) = outputDir.resolve(fileName) + + private + fun scriptPlugin(fileName: String, text: String) = PrecompiledScriptPlugin(newFile(fileName, text)) + + private + fun String.replacedBySpaces() = repeat(' ', length) +} + + +private +fun repeat(char: Char, count: Int) = String(CharArray(count) { char }) + + +private +fun doesNotExist(): Matcher = object : TypeSafeMatcher() { + + override fun describeTo(description: Description) { + description.appendText("nonexistent file") + } + + override fun matchesSafely(file: File): Boolean = !file.exists() +} diff --git a/subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ReduceGraphTest.kt b/subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ReduceGraphTest.kt new file mode 100644 index 0000000000000..8d00a1f5e39cd --- /dev/null +++ b/subprojects/kotlin-dsl-provider-plugins/src/test/kotlin/org/gradle/kotlin/dsl/provider/plugins/precompiled/tasks/ReduceGraphTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider.plugins.precompiled.tasks + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo + +import org.junit.Test + + +class ReduceGraphTest { + + @Test + fun `only external node references are left in the graph`() { + + assertThat( + reduceGraph(mapOf( + "a" to listOf("b", "c"), + "b" to listOf("e1"), + "c" to listOf("d"), + "d" to listOf("b", "e2") + )), + equalTo(mapOf( + "a" to setOf("e1", "e2"), + "b" to setOf("e1"), + "c" to setOf("e1", "e2"), + "d" to setOf("e1", "e2") + )) + ) + } +} diff --git a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/AbstractKotlinIntegrationTest.kt b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/AbstractKotlinIntegrationTest.kt index 7309eb5cbfb68..73c2b90bbca81 100644 --- a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/AbstractKotlinIntegrationTest.kt +++ b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/AbstractKotlinIntegrationTest.kt @@ -23,12 +23,15 @@ import org.gradle.integtests.fixtures.executer.ExecutionFailure import org.gradle.integtests.fixtures.executer.ExecutionResult import org.gradle.integtests.fixtures.executer.GradleContextualExecuter +import org.gradle.kotlin.dsl.resolver.GradleInstallation + import org.gradle.kotlin.dsl.support.zipTo import org.hamcrest.CoreMatchers.allOf import org.hamcrest.CoreMatchers.containsString import org.hamcrest.CoreMatchers.not import org.hamcrest.Matcher + import org.junit.Assert.assertThat import org.junit.Assume.assumeFalse import org.junit.Assume.assumeTrue @@ -129,6 +132,43 @@ abstract class AbstractKotlinIntegrationTest : AbstractIntegrationTest() { """) } + protected + fun givenPrecompiledKotlinScript(fileName: String, code: String) { + withKotlinDslPlugin() + withPrecompiledKotlinScript(fileName, code) + compileKotlin() + } + + protected + fun withPrecompiledKotlinScript(fileName: String, code: String) = + withFile("src/main/kotlin/$fileName", code) + + protected + fun withKotlinDslPlugin() = + withKotlinDslPluginIn(".") + + protected + fun withKotlinDslPluginIn(baseDir: String) = + withBuildScriptIn(baseDir, scriptWithKotlinDslPlugin()) + + protected + fun scriptWithKotlinDslPlugin(): String = + """ + plugins { + `kotlin-dsl` + } + + $repositoriesBlock + """ + + private + fun testGradleInstallation() = + GradleInstallation.Local(distribution.gradleHomeDir) + + protected + fun compileKotlin(taskName: String = "classes"): ExecutionResult = + build(taskName).assertTaskExecuted(":compileKotlin") + protected fun withClassJar(fileName: String, vararg classes: Class<*>) = withZip(fileName, classEntriesFor(*classes)) @@ -197,6 +237,11 @@ abstract class AbstractKotlinIntegrationTest : AbstractIntegrationTest() { assumeTrue("Test disabled under JDK 11 and higher", JavaVersion.current() < JavaVersion.VERSION_11) } + protected + fun assumeJava11() { + assumeTrue("Test requires Java 11 or higher", JavaVersion.current().isJava11Compatible) + } + protected fun assumeNonEmbeddedGradleExecuter() { assumeFalse(GradleContextualExecuter.isEmbedded()) diff --git a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/AbstractPluginTest.kt b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/AbstractPluginTest.kt index 43ab411fef483..3fa11f6d21a62 100644 --- a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/AbstractPluginTest.kt +++ b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/AbstractPluginTest.kt @@ -2,8 +2,11 @@ package org.gradle.kotlin.dsl.fixtures import org.gradle.util.TextUtil.normaliseFileSeparators +import org.junit.Before + import java.io.File -import java.util.* + +import java.util.Properties /** @@ -12,10 +15,15 @@ import java.util.* */ open class AbstractPluginTest : AbstractKotlinIntegrationTest() { + @Before + fun setUpDefaultSetttings() { + withDefaultSettings() + } + override val defaultSettingsScript: String get() = pluginManagementBlock - protected + private val pluginManagementBlock by lazy { """ pluginManagement { @@ -25,7 +33,7 @@ open class AbstractPluginTest : AbstractKotlinIntegrationTest() { """ } - protected + private val pluginRepositoriesBlock by lazy { """ repositories { @@ -54,8 +62,8 @@ open class AbstractPluginTest : AbstractKotlinIntegrationTest() { """ private - val futurePluginRules: String? - get() = futurePluginVersions?.entries?.joinLines { (id, version) -> + val futurePluginRules: String + get() = futurePluginVersions.entries.joinLines { (id, version) -> """ if (requested.id.id == "$id") { useVersion("$version") @@ -63,9 +71,10 @@ open class AbstractPluginTest : AbstractKotlinIntegrationTest() { """ } - private + protected val futurePluginVersions by lazy { loadPropertiesFromResource("/future-plugin-versions.properties") + ?: throw IllegalStateException("/future-plugin-versions.properties resource not found. Run intTestImage.") } private @@ -87,7 +96,7 @@ open class AbstractPluginTest : AbstractKotlinIntegrationTest() { paths.map(::normalisedPathOf) protected - fun normalisedPathOf(relativePath: String) = + fun normalisedPathOf(relativePath: String): String = normaliseFileSeparators(absolutePathOf(relativePath)) private diff --git a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/PatternMatcher.kt b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/PatternMatcher.kt deleted file mode 100644 index c487d6ad2349f..0000000000000 --- a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/PatternMatcher.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.gradle.kotlin.dsl.fixtures - -import org.hamcrest.Description -import org.hamcrest.TypeSafeMatcher - -import java.util.regex.Pattern - - -fun matches(pattern: String) = - matching(pattern) - - -fun matching(pattern: String) = - matching(pattern.toPattern()) - - -fun matching(pattern: Pattern) = - matching({ appendText("a string matching the pattern ").appendValue(pattern) }) { - pattern.matcher(this).matches() - } - - -fun matching(describe: Description.() -> Unit, match: T.() -> Boolean) = - object : TypeSafeMatcher() { - override fun matchesSafely(item: T) = match(item) - override fun describeTo(description: Description) = describe(description) - } diff --git a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/bytecode/AsmExtensions.kt b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/bytecode/AsmExtensions.kt new file mode 100644 index 0000000000000..161b7817c7977 --- /dev/null +++ b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/bytecode/AsmExtensions.kt @@ -0,0 +1,330 @@ +/* + * Copyright 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.fixtures.bytecode + +import org.jetbrains.org.objectweb.asm.ClassVisitor +import org.jetbrains.org.objectweb.asm.ClassWriter +import org.jetbrains.org.objectweb.asm.Label +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Opcodes.T_BYTE +import org.jetbrains.org.objectweb.asm.Type + +import kotlin.reflect.KClass + + +fun publicClass( + name: InternalName, + superName: InternalName? = null, + interfaces: List? = null, + classBody: ClassWriter.() -> Unit = {} +) = beginPublicClass(name, superName, interfaces).run { + classBody() + endClass() +} + + +internal +fun beginPublicClass(name: InternalName, superName: InternalName? = null, interfaces: List? = null) = + beginClass(Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL, name, superName, interfaces) + + +internal +fun beginClass( + modifiers: Int, + name: InternalName, + superName: InternalName? = null, + interfaces: List? = null +): ClassWriter = ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES).apply { + visit( + Opcodes.V1_8, + modifiers, + name.value, + null, + (superName ?: InternalNameOf.javaLangObject).value, + interfaces?.map { it.value }?.toTypedArray() + ) +} + + +internal +fun ClassWriter.endClass(): ByteArray { + visitEnd() + return toByteArray() +} + + +fun ClassWriter.publicDefaultConstructor(superName: InternalName = InternalNameOf.javaLangObject) { + publicMethod("", "()V") { + ALOAD(0) + INVOKESPECIAL(superName, "", "()V") + RETURN() + } +} + + +internal +fun ClassVisitor.publicStaticMethod( + name: String, + desc: String, + signature: String? = null, + exceptions: Array? = null, + methodBody: MethodVisitor.() -> Unit +) { + method(Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, name, desc, signature, exceptions, methodBody) +} + + +fun ClassVisitor.publicMethod( + name: String, + desc: String, + signature: String? = null, + exceptions: Array? = null, + methodBody: MethodVisitor.() -> Unit +) { + method(Opcodes.ACC_PUBLIC, name, desc, signature, exceptions, methodBody) +} + + +internal +fun ClassVisitor.method( + access: Int, + name: String, + desc: String, + signature: String? = null, + exceptions: Array? = null, + methodBody: MethodVisitor.() -> Unit +) { + visitMethod(access, name, desc, signature, exceptions).apply { + visitCode() + methodBody() + visitMaxs(0, 0) + visitEnd() + } +} + + +internal +fun MethodVisitor.loadByteArray(byteArray: ByteArray) { + LDC(byteArray.size) + NEWARRAY(T_BYTE) + for ((i, byte) in byteArray.withIndex()) { + DUP() + LDC(i) + LDC(byte) + BASTORE() + } +} + + +internal +fun MethodVisitor.NEW(type: InternalName) { + visitTypeInsn(Opcodes.NEW, type) +} + + +internal +fun MethodVisitor.visitTypeInsn(opcode: Int, type: InternalName) { + visitTypeInsn(opcode, type.value) +} + + +internal +fun MethodVisitor.NEWARRAY(primitiveType: Int) { + visitIntInsn(Opcodes.NEWARRAY, primitiveType) +} + + +internal +fun MethodVisitor.LDC(type: InternalName) { + visitLdcInsn(Type.getType("L${type.value};")) +} + + +internal +fun MethodVisitor.LDC(value: Any) { + visitLdcInsn(value) +} + + +internal +fun MethodVisitor.INVOKEVIRTUAL(owner: InternalName, name: String, desc: String, itf: Boolean = false) { + visitMethodInsn_(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf) +} + + +internal +fun MethodVisitor.INVOKESPECIAL(owner: InternalName, name: String, desc: String, itf: Boolean = false) { + visitMethodInsn_(Opcodes.INVOKESPECIAL, owner, name, desc, itf) +} + + +internal +fun MethodVisitor.INVOKEINTERFACE(owner: InternalName, name: String, desc: String, itf: Boolean = true) { + visitMethodInsn_(Opcodes.INVOKEINTERFACE, owner, name, desc, itf) +} + + +internal +fun MethodVisitor.INVOKESTATIC(owner: InternalName, name: String, desc: String) { + visitMethodInsn_(Opcodes.INVOKESTATIC, owner, name, desc, false) +} + + +private +fun MethodVisitor.visitMethodInsn_(opcode: Int, owner: InternalName, name: String, desc: String, itf: Boolean) { + visitMethodInsn(opcode, owner.value, name, desc, itf) +} + + +internal +fun MethodVisitor.BASTORE() { + visitInsn(Opcodes.BASTORE) +} + + +internal +fun MethodVisitor.DUP() { + visitInsn(Opcodes.DUP) +} + + +internal +fun MethodVisitor.ARETURN() { + visitInsn(Opcodes.ARETURN) +} + + +fun MethodVisitor.RETURN() { + visitInsn(Opcodes.RETURN) +} + + +internal +fun MethodVisitor.ALOAD(`var`: Int) { + visitVarInsn(Opcodes.ALOAD, `var`) +} + + +internal +fun MethodVisitor.ASTORE(`var`: Int) { + visitVarInsn(Opcodes.ASTORE, `var`) +} + + +internal +fun MethodVisitor.GOTO(label: Label) { + visitJumpInsn(Opcodes.GOTO, label) +} + + +internal +inline fun MethodVisitor.TRY_CATCH( + noinline tryBlock: MethodVisitor.() -> Unit, + noinline catchBlock: MethodVisitor.() -> Unit +) = + TRY_CATCH(T::class.internalName, tryBlock, catchBlock) + + +internal +fun MethodVisitor.TRY_CATCH( + exceptionType: InternalName, + tryBlock: MethodVisitor.() -> Unit, + catchBlock: MethodVisitor.() -> Unit +) { + + val tryBlockStart = Label() + val tryBlockEnd = Label() + val catchBlockStart = Label() + val catchBlockEnd = Label() + visitTryCatchBlock(tryBlockStart, tryBlockEnd, catchBlockStart, exceptionType.value) + + visitLabel(tryBlockStart) + tryBlock() + GOTO(catchBlockEnd) + visitLabel(tryBlockEnd) + + visitLabel(catchBlockStart) + catchBlock() + visitLabel(catchBlockEnd) +} + + +internal +fun > MethodVisitor.GETSTATIC(field: T) { + val owner = field.declaringClass.internalName + GETSTATIC(owner, field.name, "L$owner;") +} + + +internal +fun MethodVisitor.GETSTATIC(owner: InternalName, name: String, desc: String) { + visitFieldInsn(Opcodes.GETSTATIC, owner.value, name, desc) +} + + +internal +fun MethodVisitor.GETFIELD(owner: InternalName, name: String, desc: String) { + visitFieldInsn(Opcodes.GETFIELD, owner.value, name, desc) +} + + +internal +fun MethodVisitor.PUTFIELD(owner: InternalName, name: String, desc: String) { + visitFieldInsn(Opcodes.PUTFIELD, owner.value, name, desc) +} + + +internal +fun MethodVisitor.CHECKCAST(type: InternalName) { + visitTypeInsn(Opcodes.CHECKCAST, type) +} + + +internal +fun MethodVisitor.ACONST_NULL() { + visitInsn(Opcodes.ACONST_NULL) +} + + +/** + * A JVM internal type name (as in `java/lang/Object` instead of `java.lang.Object`). + */ +@Suppress("experimental_feature_warning") +inline class InternalName(val value: String) { + + companion object { + fun from(sourceName: String) = InternalName(sourceName.replace('.', '/')) + } + + override fun toString() = value +} + + +internal +object InternalNameOf { + + val javaLangObject = InternalName("java/lang/Object") +} + + +val KClass<*>.internalName: InternalName + get() = java.internalName + + +inline val Class<*>.internalName: InternalName + get() = InternalName(Type.getInternalName(this)) diff --git a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/plugins.kt b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/plugins.kt new file mode 100644 index 0000000000000..b872922d9c23b --- /dev/null +++ b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/plugins.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.fixtures + + +fun pluginDescriptorEntryFor(id: String, implClass: String) = + "META-INF/gradle-plugins/$id.properties" to "implementation-class=$implClass".toByteArray() diff --git a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/zipUtils.kt b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/zipUtils.kt index ae4f059332e2a..085c895e26206 100644 --- a/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/zipUtils.kt +++ b/subprojects/kotlin-dsl-test-fixtures/src/main/kotlin/org/gradle/kotlin/dsl/fixtures/zipUtils.kt @@ -3,12 +3,19 @@ package org.gradle.kotlin.dsl.fixtures import kotlin.reflect.KClass -fun classEntriesFor(classes: Array>) = - classEntriesFor(*classes.map { it.java }.toTypedArray()) +fun classEntriesFor(classes: Array>): Sequence> = + classes.asSequence().map { classEntryFor(it) } fun classEntriesFor(vararg classes: Class<*>): Sequence> = - classes.asSequence().map { - val classFilePath = it.name.replace('.', '/') + ".class" - classFilePath to it.getResource("/$classFilePath").readBytes() - } + classes.asSequence().map { classEntryFor(it) } + + +fun classEntryFor(clazz: KClass<*>): Pair = + classEntryFor(clazz.java) + + +fun classEntryFor(clazz: Class<*>): Pair { + val classFilePath = clazz.name.replace('.', '/') + ".class" + return classFilePath to clazz.getResource("/$classFilePath").readBytes() +} diff --git a/subprojects/kotlin-dsl-test-fixtures/src/test/kotlin/org/gradle/kotlin/dsl/fixtures/SimplifiedKotlinScriptEvaluatorTest.kt b/subprojects/kotlin-dsl-test-fixtures/src/test/kotlin/org/gradle/kotlin/dsl/fixtures/SimplifiedKotlinScriptEvaluatorTest.kt index 5151c746dd8e4..7bbf0a85f1e1b 100644 --- a/subprojects/kotlin-dsl-test-fixtures/src/test/kotlin/org/gradle/kotlin/dsl/fixtures/SimplifiedKotlinScriptEvaluatorTest.kt +++ b/subprojects/kotlin-dsl-test-fixtures/src/test/kotlin/org/gradle/kotlin/dsl/fixtures/SimplifiedKotlinScriptEvaluatorTest.kt @@ -16,10 +16,13 @@ package org.gradle.kotlin.dsl.fixtures +import com.nhaarman.mockito_kotlin.any +import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.verify import org.gradle.api.Project +import org.gradle.api.initialization.IncludedBuild import org.gradle.api.initialization.Settings import org.gradle.api.invocation.Gradle @@ -57,7 +60,10 @@ class SimplifiedKotlinScriptEvaluatorTest : TestWithTempFiles() { @Test fun `can eval script against Gradle mock`() { - val gradle = mock() + val includedBuild = mock() + val gradle = mock() { + on { includedBuild(any()) } doReturn includedBuild + } eval( script = """ includedBuild("foo") diff --git a/subprojects/kotlin-dsl-tooling-builders/kotlin-dsl-tooling-builders.gradle.kts b/subprojects/kotlin-dsl-tooling-builders/kotlin-dsl-tooling-builders.gradle.kts index 35d1d892e5e0b..236bd33c50b47 100644 --- a/subprojects/kotlin-dsl-tooling-builders/kotlin-dsl-tooling-builders.gradle.kts +++ b/subprojects/kotlin-dsl-tooling-builders/kotlin-dsl-tooling-builders.gradle.kts @@ -14,7 +14,6 @@ * limitations under the License. */ -import build.withCompileOnlyGradleApiModulesWithParameterNames import org.gradle.gradlebuild.unittestandcompile.ModuleType plugins { @@ -27,7 +26,6 @@ gradlebuildJava { moduleType = ModuleType.CORE } -withCompileOnlyGradleApiModulesWithParameterNames(":plugins") dependencies { @@ -35,9 +33,21 @@ dependencies { compile(project(":kotlinDsl")) + compileOnly(project(":plugins")) + testImplementation(project(":kotlinDslTestFixtures")) integTestImplementation(project(":kotlinDslTestFixtures")) integTestRuntimeOnly(project(":toolingApiBuilders")) + + crossVersionTestRuntimeOnly(project(":pluginDevelopment")) +} + +tasks { + + // TODO:kotlin-dsl + verifyTestFilesCleanup { + enabled = false + } } diff --git a/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/AbstractKotlinScriptModelCrossVersionTest.groovy b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/AbstractKotlinScriptModelCrossVersionTest.groovy new file mode 100644 index 0000000000000..d73ae5bdc3817 --- /dev/null +++ b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/AbstractKotlinScriptModelCrossVersionTest.groovy @@ -0,0 +1,344 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.tooling.builders + +import groovy.transform.CompileStatic + +import org.gradle.integtests.tooling.fixture.TextUtil +import org.gradle.integtests.tooling.fixture.ToolingApiAdditionalClasspath +import org.gradle.integtests.tooling.fixture.ToolingApiSpecification +import org.gradle.integtests.tooling.fixture.ToolingApiVersion +import org.gradle.test.fixtures.archive.JarTestFixture +import org.gradle.test.fixtures.file.TestFile + +import org.gradle.kotlin.dsl.tooling.models.KotlinBuildScriptModel + +import org.hamcrest.Description +import org.hamcrest.Matcher +import org.hamcrest.TypeSafeMatcher + +import java.util.function.Consumer +import java.util.function.Predicate +import java.util.regex.Pattern +import java.util.zip.ZipOutputStream + +import static org.gradle.kotlin.dsl.resolver.KotlinBuildScriptModelRequestKt.fetchKotlinBuildScriptModelFor + +import static org.hamcrest.CoreMatchers.allOf +import static org.hamcrest.CoreMatchers.hasItem +import static org.hamcrest.CoreMatchers.hasItems +import static org.hamcrest.CoreMatchers.not +import static org.junit.Assert.assertThat + + +class ProjectSourceRoots { + + final File projectDir + final List sourceSets + final List languages + + ProjectSourceRoots(File projectDir, List sourceSets, List languages) { + this.projectDir = projectDir + this.sourceSets = sourceSets + this.languages = languages + } +} + + +@ToolingApiVersion(">=4.1") +@ToolingApiAdditionalClasspath(KotlinDslToolingModelsClasspathProvider) +@CompileStatic +abstract class AbstractKotlinScriptModelCrossVersionTest extends ToolingApiSpecification { + + def setup() { + // Required for the lenient classpath mode + toolingApi.requireDaemons() + } + + private String defaultSettingsScript = "" + + protected String repositoriesBlock = """ + repositories { + gradlePluginPortal() + } + """.stripIndent() + + private String targetKotlinVersion + + protected String getTargetKotlinVersion() { + if (targetKotlinVersion == null) { + targetKotlinVersion = loadTargetDistKotlinVersion() + } + return targetKotlinVersion + } + + private static String loadTargetDistKotlinVersion() { + def props = new JarTestFixture(targetDist.gradleHomeDir.file("lib").listFiles().find { + it.name.startsWith("gradle-kotlin-dsl-${targetVersion.baseVersion.version}") + }).content("gradle-kotlin-dsl-versions.properties") + return new Properties().tap { load(new StringReader(props)) }.getProperty("kotlin") + } + + protected TestFile withDefaultSettings() { + return withSettings(defaultSettingsScript) + } + + protected TestFile withSettings(String script) { + return withSettingsIn(".", script) + } + + protected TestFile withDefaultSettingsIn(String baseDir) { + return withSettingsIn(baseDir, defaultSettingsScript) + } + + private TestFile withSettingsIn(String baseDir, String script) { + return withFile("$baseDir/settings.gradle.kts", script) + } + + protected TestFile withBuildScript(String script = "") { + return withBuildScriptIn(".", script) + } + + protected TestFile withBuildScriptIn(String baseDir, String script = "") { + return withFile("$baseDir/build.gradle.kts", script) + } + + protected TestFile withFile(String path, String content = "") { + return file(path).tap { text = content.stripIndent() } + } + + protected TestFile withEmptyJar(String path) { + return file(path).tap { jarFile -> + jarFile.parentFile.mkdirs() + new ZipOutputStream(jarFile.newOutputStream()).close() + } + } + + protected void withBuildSrc() { + projectDir.createFile("buildSrc/src/main/groovy/build/Foo.groovy") << """ + package build + class Foo {} + """.stripIndent() + } + + protected void withKotlinBuildSrc() { + withDefaultSettingsIn("buildSrc") + withBuildScriptIn("buildSrc", """ + plugins { + `kotlin-dsl` + } + + $repositoriesBlock + """) + } + + protected ProjectSourceRoots[] withMultiProjectKotlinBuildSrc() { + withDefaultSettingsIn("buildSrc").append(""" + include(":a", ":b", ":c") + """) + withFile("buildSrc/build.gradle.kts", """ + plugins { + java + `kotlin-dsl` apply false + } + + val kotlinDslProjects = listOf(project.project(":a"), project.project(":b")) + + kotlinDslProjects.forEach { + it.apply(plugin = "org.gradle.kotlin.kotlin-dsl") + } + + dependencies { + kotlinDslProjects.forEach { + "runtime"(project(it.path)) + } + } + + allprojects { + $repositoriesBlock + } + """) + withFile("buildSrc/b/build.gradle.kts", """dependencies { implementation(project(":c")) }""") + withFile("buildSrc/c/build.gradle.kts", "plugins { java }") + + return [ + withMainSourceSetJavaIn("buildSrc"), + withMainSourceSetJavaKotlinIn("buildSrc/a"), + withMainSourceSetJavaKotlinIn("buildSrc/b"), + withMainSourceSetJavaIn("buildSrc/c") + ] as ProjectSourceRoots[] + } + + protected List canonicalClassPath() { + return canonicalClassPathFor(projectDir) + } + + protected List canonicalClassPathFor(File projectDir, File scriptFile = null) { + return canonicalClasspathOf(kotlinBuildScriptModelFor(projectDir, scriptFile)) + } + + protected List classPathFor(File projectDir, File scriptFile = null) { + return kotlinBuildScriptModelFor(projectDir, scriptFile).classPath + } + + protected List sourcePathFor(File scriptFile) { + kotlinBuildScriptModelFor(projectDir, scriptFile).sourcePath + } + + protected KotlinBuildScriptModel kotlinBuildScriptModelFor(File projectDir, File scriptFile = null) { + return fetchKotlinBuildScriptModelFor( + projectDir, + scriptFile, + { selectedProjectDir -> connector().forProjectDirectory(selectedProjectDir) } + ) + } + + protected static List canonicalClasspathOf(KotlinBuildScriptModel model) { + return model.classPath.collect { it.canonicalFile } + } + + protected static void assertClassPathContains(List classPath, File... expectedFiles) { + assertThat( + classPath.collect { it.name } as List, + hasItems(fileNameSetOf(expectedFiles)) + ) + } + + protected void assertClassPathContains(File... expectedFiles) { + assertThat( + canonicalClassPath().collect { it.name } as List, + hasItems(fileNameSetOf(expectedFiles)) + ) + } + + protected static void assertIncludes(List classPath, File... files) { + assertThat( + classPath.collect { it.name } as List, + hasItems(fileNameSetOf(files)) + ) + } + + protected static void assertExcludes(List classPath, File... files) { + assertThat( + classPath.collect { it.name } as List, + not(hasItems(fileNameSetOf(files))) + ) + } + + private static String[] fileNameSetOf(File... files) { + return files.collect { it.name }.toSet() as String[] + } + + protected static void assertContainsGradleKotlinDslJars(List classPath) { + def version = "[0-9.]+(-.+?)?" + assertThat( + classPath.collect { it.name } as List, + hasItems( + matching("gradle-kotlin-dsl-$version\\.jar"), + matching("gradle-api-$version\\.jar"), + matching("gradle-kotlin-dsl-extensions-$version\\.jar") + ) + ) + } + + protected void assertClassPathFor( + File buildScript, + Set includes, + Set excludes, + File importedProjectDir = projectDir + ) { + def includeItems = hasItems(includes.collect { it.name } as String[]) + def excludeItems = not(hasItems(excludes.collect { it.name } as String[])) + def condition = excludes.isEmpty() ? includeItems : allOf(includeItems, excludeItems) + assertThat( + classPathFor(importedProjectDir, buildScript).collect { it.name }, + condition + ) + } + + protected static void assertContainsBuildSrc(List classPath) { + assertThat( + classPath.collect { it.name } as List, + hasBuildSrc() + ) + } + + protected static Matcher> hasBuildSrc() { + hasItem("buildSrc.jar") + } + + private ProjectSourceRoots withMainSourceSetJavaIn(String projectDir) { + return new ProjectSourceRoots(file(projectDir), ["main"], ["java"]) + } + + protected ProjectSourceRoots withMainSourceSetJavaKotlinIn(String projectDir) { + return new ProjectSourceRoots(file(projectDir), ["main"], ["java", "kotlin"]) + } + + protected static Matcher> matchesProjectsSourceRoots(ProjectSourceRoots... projectSourceRoots) { + return allOf(projectSourceRoots.findAll { !it.languages.isEmpty() }.collectMany { sourceRoots -> + + def languageDirs = + sourceRoots.sourceSets.collectMany { sourceSet -> + ["java", "kotlin"].collect { language -> + def hasLanguageDir = hasLanguageDir(sourceRoots.projectDir, sourceSet, language) + if (language in sourceRoots.languages) { + hasLanguageDir + } else { + not(hasLanguageDir) + } + } as Collection + } + + def resourceDirs = + sourceRoots.sourceSets.collect { sourceSet -> + hasLanguageDir(sourceRoots.projectDir, sourceSet, "resources") + } as Collection + + languageDirs + resourceDirs + }) + } + + private static Matcher> hasLanguageDir(File base, String set, String lang) { + return hasItem(new File(base, "src/$set/$lang")) + } + + protected static Matcher matching(String pattern) { + def compiledPattern = Pattern.compile(pattern) + return matching({ it.appendText("a string matching the pattern").appendValue(pattern) }) { String item -> + compiledPattern.matcher(item).matches() + } + } + + protected static Matcher matching(Consumer describe, Predicate match) { + return new TypeSafeMatcher() { + @Override + protected boolean matchesSafely(T item) { + return match.test(item) + } + + @Override + void describeTo(Description description) { + describe.accept(description) + } + } + } + + protected static String normalizedPathOf(File file) { + return TextUtil.normaliseFileSeparators(file.path) + } +} diff --git a/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/KotlinDslToolingModelsClasspathProvider.groovy b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/KotlinDslToolingModelsClasspathProvider.groovy new file mode 100644 index 0000000000000..34ebfa32c3574 --- /dev/null +++ b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/KotlinDslToolingModelsClasspathProvider.groovy @@ -0,0 +1,36 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.tooling.builders + +import org.gradle.integtests.fixtures.executer.GradleDistribution +import org.gradle.integtests.tooling.fixture.ToolingApiAdditionalClasspathProvider + + +/** + * Provides TAPI client additional classpath as found in Kotlin IDEs. + */ +class KotlinDslToolingModelsClasspathProvider implements ToolingApiAdditionalClasspathProvider { + + @Override + List additionalClasspathFor(GradleDistribution distribution) { + distribution.gradleHomeDir.file("lib").listFiles().findAll { file -> + file.name.startsWith("gradle-kotlin-dsl-") || file.name.startsWith("kotlin-stdlib-") + }.tap { classpath -> + assert classpath.size() >= 3 + } + } +} diff --git a/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r41/KotlinBuildScriptTemplateModelCrossVersionSpec.groovy b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r41/KotlinBuildScriptTemplateModelCrossVersionSpec.groovy new file mode 100644 index 0000000000000..90735b762330d --- /dev/null +++ b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r41/KotlinBuildScriptTemplateModelCrossVersionSpec.groovy @@ -0,0 +1,54 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.tooling.builders.r41 + +import org.gradle.integtests.tooling.fixture.TargetGradleVersion + +import org.gradle.kotlin.dsl.tooling.builders.AbstractKotlinScriptModelCrossVersionTest +import org.gradle.kotlin.dsl.tooling.models.KotlinBuildScriptTemplateModel + + +@TargetGradleVersion(">=4.1") +class KotlinBuildScriptTemplateModelCrossVersionSpec extends AbstractKotlinScriptModelCrossVersionTest { + + def "can load script template using classpath model"() { + + given: + settingsFile << "" + + when: + def model = loadToolingModel(KotlinBuildScriptTemplateModel) + + then: + loadClassesFrom( + model.classPath, + "org.gradle.kotlin.dsl.KotlinBuildScript", + "org.gradle.kotlin.dsl.resolver.KotlinBuildScriptDependenciesResolver") + } + + private static void loadClassesFrom(List classPath, String... classNames) { + classLoaderFor(classPath).withCloseable { loader -> + classNames.each { + loader.loadClass(it) + } + } + } + + private static URLClassLoader classLoaderFor(List classPath) { + new URLClassLoader(classPath.collect { it.toURI().toURL() } as URL[]) + } +} diff --git a/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/AccessorsClassPathModelCrossVersionSpec.groovy b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/AccessorsClassPathModelCrossVersionSpec.groovy new file mode 100644 index 0000000000000..0dc64411c68e7 --- /dev/null +++ b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/AccessorsClassPathModelCrossVersionSpec.groovy @@ -0,0 +1,122 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.tooling.builders.r54 + +import org.gradle.integtests.tooling.fixture.TargetGradleVersion + +import org.gradle.kotlin.dsl.tooling.builders.AbstractKotlinScriptModelCrossVersionTest + +import org.hamcrest.Matcher + +import javax.annotation.Nullable + +import static org.hamcrest.CoreMatchers.equalTo +import static org.hamcrest.CoreMatchers.hasItem +import static org.hamcrest.CoreMatchers.not +import static org.junit.Assert.assertThat + + +@TargetGradleVersion(">=5.4") +class AccessorsClassPathModelCrossVersionSpec extends AbstractKotlinScriptModelCrossVersionTest { + + def "classpath model includes jit accessors by default"() { + + given: + withDefaultSettings() + withBuildScript(""" + plugins { java } + """) + + expect: + assertAccessorsInClassPathOf(buildFileKts) + } + + def "jit accessors can be turned off"() { + + given: + withDefaultSettings() + withBuildScript(""" + plugins { java } + """) + + and: + withFile("gradle.properties", "org.gradle.kotlin.dsl.accessors=off") + + expect: + assertThat( + classPathFor(projectDir, buildFile), + not(hasAccessorsClasses()) + ) + } + + def "the set of jit accessors is a function of the set of applied plugins"() { + + given: + // TODO:accessors - rework this test to ensure it's providing enough coverage + def s1 = setOfAutomaticAccessorsFor(["application"]) + def s2 = setOfAutomaticAccessorsFor(["java"]) + def s3 = setOfAutomaticAccessorsFor(["application"]) + def s4 = setOfAutomaticAccessorsFor(["application", "java"]) + def s5 = setOfAutomaticAccessorsFor(["java"]) + + expect: + assertThat(s1, not(equalTo(s2))) // application ≠ java + assertThat(s1, equalTo(s3)) // application = application + assertThat(s2, equalTo(s5)) // java = java + assertThat(s1, equalTo(s4)) // application ⊇ java + } + + private void assertAccessorsInClassPathOf(File buildFile) { + def model = kotlinBuildScriptModelFor(projectDir, buildFile) + assertThat(model.classPath, hasAccessorsClasses()) + assertThat(model.sourcePath, hasAccessorsSource()) + } + + private Matcher> hasAccessorsClasses() { + return hasItem( + matching({ it.appendText("accessors classes") }) { File file -> + new File(file, accessorsClassFilePath).isFile() + } + ) + } + + private Matcher> hasAccessorsSource() { + return hasItem( + matching({ it.appendText("accessors source") }) { File file -> + new File(file, accessorsSourceFilePath).isFile() + } + ) + } + + private File setOfAutomaticAccessorsFor(Iterable plugins) { + withDefaultSettings() + def buildFile = withBuildScript("plugins {\n${plugins.join("\n")}\n}") + def classFilePath = accessorsClassFor(buildFile).toPath() + return projectDir.toPath().relativize(classFilePath).toFile() + } + + @Nullable + private File accessorsClassFor(File buildFile) { + return classPathFor(projectDir, buildFile) + .tap { println(it) } + .find { it.isDirectory() && new File(it, accessorsClassFilePath).isFile() } + } + + private String accessorsClassFilePath = "org/gradle/kotlin/dsl/ArchivesConfigurationAccessorsKt.class" + + private String accessorsSourceFilePath = "org/gradle/kotlin/dsl/ArchivesConfigurationAccessors.kt" +} diff --git a/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinBuildScriptModelCrossVersionSpec.groovy b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinBuildScriptModelCrossVersionSpec.groovy new file mode 100644 index 0000000000000..88bec082afc83 --- /dev/null +++ b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinBuildScriptModelCrossVersionSpec.groovy @@ -0,0 +1,585 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.tooling.builders.r54 + +import groovy.transform.CompileStatic + +import org.gradle.integtests.tooling.fixture.TargetGradleVersion +import org.gradle.test.fixtures.file.LeaksFileHandles + +import org.gradle.kotlin.dsl.tooling.builders.AbstractKotlinScriptModelCrossVersionTest + +import org.hamcrest.Matcher + +import static org.hamcrest.CoreMatchers.allOf +import static org.hamcrest.CoreMatchers.equalTo +import static org.hamcrest.CoreMatchers.not +import static org.hamcrest.CoreMatchers.hasItem +import static org.hamcrest.CoreMatchers.hasItems +import static org.junit.Assert.assertThat +import static org.junit.Assert.assertTrue + + +@TargetGradleVersion(">=5.4") +class KotlinBuildScriptModelCrossVersionSpec extends AbstractKotlinScriptModelCrossVersionTest { + + def "can fetch buildSrc classpath in face of compilation errors"() { + + given: + withBuildSrc() + + and: + withDefaultSettings() + withBuildScript(""" + val p = + """) + + expect: + assertContainsBuildSrc(canonicalClassPath()) + } + + def "can fetch buildSrc classpath in face of buildscript exceptions"() { + + given: + withBuildSrc() + + and: + withDefaultSettings() + withBuildScript(""" + buildscript { TODO() } + """) + + expect: + assertContainsBuildSrc(canonicalClassPath()) + } + + def "can fetch buildscript classpath in face of compilation errors"() { + + given: + withEmptyJar("classes.jar") + withDefaultSettings() + withBuildScript(""" + buildscript { + dependencies { + classpath(files("classes.jar")) + } + } + + val p = + """) + + expect: + assertClassPathContains( + file("classes.jar") + ) + } + + @LeaksFileHandles("Kotlin compiler daemon on buildSrc jar") + def "can fetch classpath in face of buildSrc test failures"() { + + given: + withKotlinBuildSrc() + withFile("buildSrc/build.gradle.kts").tap { buildSrcScript -> + buildSrcScript.text = buildSrcScript.text + """ + dependencies { + testImplementation("junit:junit:4.12") + } + """ + } + withFile("buildSrc/src/test/kotlin/FailingTest.kt", """ + class FailingTest { + @org.junit.Test fun test() { + throw Exception("BOOM") + } + } + """) + withDefaultSettings() + + expect: + assertContainsBuildSrc(canonicalClassPath()) + } + + def "can fetch buildscript classpath of top level Groovy script"() { + + given: + withBuildSrc() + + withEmptyJar("classes.jar") + + withDefaultSettings() + withFile("build.gradle", """ + buildscript { + dependencies { + classpath(files("classes.jar")) + } + } + """) + + when: + def classPath = canonicalClassPath() + + then: + assertThat( + classPath.collect { it.name } as List, + hasItems("classes.jar") + ) + + assertContainsBuildSrc(classPath) + + assertContainsGradleKotlinDslJars(classPath) + } + + def "can fetch buildscript classpath for sub-project script when parent has errors"() { + + given: + withDefaultSettings().append(""" + include("sub") + """) + + withBuildScript("val p =") + + def jar = withEmptyJar("libs/jar.jar") + + def subProjectScript = + withFile("sub/build.gradle.kts", """ + buildscript { + dependencies { classpath(files("${normalizedPathOf(jar)}")) } + } + """) + + expect: + assertClassPathFor( + subProjectScript, + [jar] as Set, + [] as Set + ) + } + + def "can fetch buildscript classpath for sub-project script"() { + + expect: + assertCanFetchClassPathForSubProjectScriptIn(".") + } + + def "can fetch buildscript classpath for sub-project script of nested project"() { + + given: + withDefaultSettings() + + expect: + assertCanFetchClassPathForSubProjectScriptIn("nested-project") + } + + @CompileStatic + private void assertCanFetchClassPathForSubProjectScriptIn(String location) { + + withDefaultSettingsIn(location).append(""" + include("foo", "bar") + """) + + def parentJar = withEmptyJar("$location/libs/parent.jar") + def fooJar = withEmptyJar("$location/libs/foo.jar") + def barJar = withEmptyJar("$location/libs/bar.jar") + + assertTrue parentJar.isFile() + assertTrue fooJar.isFile() + assertTrue barJar.isFile() + + def parentBuildScript = withFile("$location/build.gradle.kts", buildScriptDependencyOn(parentJar)) + def fooBuildScript = withFile("$location/foo/build.gradle.kts", buildScriptDependencyOn(fooJar)) + def barBuildScript = withFile("$location/bar/build.gradle.kts", buildScriptDependencyOn(barJar)) + + assertTrue parentBuildScript.text.contains(normalizedPathOf(parentJar)) + assertTrue fooBuildScript.text.contains(normalizedPathOf(fooJar)) + assertTrue barBuildScript.text.contains(normalizedPathOf(barJar)) + + assertClassPathFor( + parentBuildScript, + [parentJar] as Set, + [fooJar, barJar] as Set, + projectDir + ) + + assertClassPathFor( + fooBuildScript, + [parentJar, fooJar] as Set, + [barJar] as Set, + projectDir + ) + + assertClassPathFor( + barBuildScript, + [parentJar, barJar] as Set, + [fooJar] as Set, + projectDir + ) + } + + def "can fetch buildscript classpath for sub-project script outside root project dir"() { + + given: + def rootDependency = withEmptyJar("libs/root.jar") + def subDependency = withEmptyJar("libs/sub.jar") + + and: + withDefaultSettingsIn("root").append(""" + include("sub") + project(":sub").apply { + projectDir = file("../sub") + buildFileName = "sub.gradle.kts" + } + """) + file("sub").mkdirs() + + and: + def rootBuildScript = withFile("root/build.gradle", buildScriptDependencyOn(rootDependency)) + def subBuildScript = withFile("sub/sub.gradle.kts", buildScriptDependencyOn(subDependency)) + def rootProjectDir = rootBuildScript.parentFile + + expect: + assertClassPathFor( + rootBuildScript, + [rootDependency] as Set, + [subDependency] as Set, + rootProjectDir + ) + + and: + assertClassPathFor( + subBuildScript, + [rootDependency, subDependency] as Set, + [] as Set, + rootProjectDir + ) + } + + def "can fetch buildscript classpath for buildSrc sub-project script outside buildSrc root"() { + + expect: + assertCanFetchClassPathForSubProjectScriptOfNestedProjectOutsideProjectRoot("buildSrc") + } + + def "can fetch buildscript classpath for sub-project script of nested project outside nested project root"() { + + when: + // This use-case was never supported and continues not to be supported + assertCanFetchClassPathForSubProjectScriptOfNestedProjectOutsideProjectRoot("nested-project") + + then: + thrown(AssertionError) + } + + private void assertCanFetchClassPathForSubProjectScriptOfNestedProjectOutsideProjectRoot(String nestedProjectName) { + + withDefaultSettings() + + def rootDependency = withEmptyJar("libs/root-dep.jar") + def nestedRootDependency = withEmptyJar("libs/$nestedProjectName-root-dep.jar") + def nestedSubDependency = withEmptyJar("libs/$nestedProjectName-sub-dep.jar") + + withDefaultSettingsIn(nestedProjectName).append(""" + include("sub") + project(":sub").apply { + projectDir = file("../$nestedProjectName-sub") + buildFileName = "sub.gradle.kts" + } + """) + file("$nestedProjectName-sub").mkdirs() + + def rootBuildScript = withFile("build.gradle", buildScriptDependencyOn(rootDependency)) + def nestedBuildScript = withFile("$nestedProjectName/build.gradle.kts", buildScriptDependencyOn(nestedRootDependency)) + def nestedSubBuildScript = withFile("$nestedProjectName-sub/sub.gradle.kts", buildScriptDependencyOn(nestedSubDependency)) + + assertClassPathFor( + rootBuildScript, + [rootDependency] as Set, + [nestedRootDependency, nestedSubDependency] as Set + ) + + assertClassPathFor( + nestedBuildScript, + [nestedRootDependency] as Set, + [rootDependency, nestedSubDependency] as Set + ) + + assertClassPathFor( + nestedSubBuildScript, + [nestedRootDependency, nestedSubDependency] as Set, + [rootDependency] as Set + ) + } + + def "can fetch classpath of script plugin"() { + + expect: + assertCanFetchClassPathOfScriptPlugin("") + } + + def "can fetch classpath of script plugin with compilation errors"() { + + expect: + assertCanFetchClassPathOfScriptPlugin("val p = ") + } + + def "can fetch classpath of script plugin with buildscript block compilation errors"() { + + expect: + assertCanFetchClassPathOfScriptPlugin("buildscript { val p = }") + } + + private void assertCanFetchClassPathOfScriptPlugin(String scriptPluginCode) { + + withBuildSrc() + + def buildSrcDependency = + withEmptyJar("buildSrc-dependency.jar") + + withFile("buildSrc/build.gradle", """ + dependencies { compile(files("../${buildSrcDependency.name}")) } + """) + + def rootProjectDependency = withEmptyJar("rootProject-dependency.jar") + + withDefaultSettings() + withFile("build.gradle", """ + buildscript { + dependencies { classpath(files("${rootProjectDependency.name}")) } + } + """) + + def scriptPlugin = withFile("plugin.gradle.kts", scriptPluginCode) + + def scriptPluginClassPath = canonicalClassPathFor(projectDir, scriptPlugin) + assertThat( + scriptPluginClassPath.collect { it.name }, + allOf( + not(hasItem(rootProjectDependency.name)), + hasItem(buildSrcDependency.name) + ) + ) + assertContainsBuildSrc(scriptPluginClassPath) + assertContainsGradleKotlinDslJars(scriptPluginClassPath) + } + + + def "can fetch classpath of plugin portal plugin in plugins block"() { + + given: + withDefaultSettings() + withBuildScript(""" + plugins { + id("org.gradle.hello-world") version "0.2" + } + """) + + expect: + assertThat( + canonicalClassPath().collect { it.name } as List, + hasItems("gradle-hello-world-plugin-0.2.jar") + ) + } + + def "can fetch classpath of script plugin with buildscript block"() { + + given: + withDefaultSettings() + + def scriptPluginDependency = + withEmptyJar("script-plugin-dependency.jar") + + def scriptPlugin = withFile("plugin.gradle.kts", """ + buildscript { + dependencies { classpath(files("${scriptPluginDependency.name}")) } + } + + // Shouldn't be evaluated + throw IllegalStateException() + """) + + when: + def model = kotlinBuildScriptModelFor(projectDir, scriptPlugin) + + then: + assertThat( + "Script body shouldn't be evaluated", + model.exceptions, + equalTo([]) + ) + + and: + def scriptPluginClassPath = canonicalClasspathOf(model) + assertThat( + scriptPluginClassPath.collect { it.name } as List, + hasItem(scriptPluginDependency.name) + ) + + assertContainsGradleKotlinDslJars(scriptPluginClassPath) + } + + def "sourcePath includes Gradle sources"() { + + expect: + assertSourcePathIncludesGradleSourcesGiven("", "") + } + + def "sourcePath includes kotlin-stdlib sources resolved against project"() { + + expect: + assertSourcePathIncludesKotlinStdlibSourcesGiven( + "", + "buildscript { $repositoriesBlock }" + ) + } + + def "sourcePath includes kotlin-stdlib sources resolved against project hierarchy"() { + + expect: + assertSourcePathIncludesKotlinStdlibSourcesGiven( + "buildscript { $repositoriesBlock }", + "" + ) + } + + def "sourcePath includes buildscript classpath sources resolved against project"() { + + expect: + assertSourcePathIncludesKotlinPluginSourcesGiven( + "", + """ + buildscript { + dependencies { classpath(embeddedKotlin("gradle-plugin")) } + $repositoriesBlock + } + """ + ) + } + + def "sourcePath includes buildscript classpath sources resolved against project hierarchy"() { + + expect: + assertSourcePathIncludesKotlinPluginSourcesGiven( + """ + buildscript { + dependencies { classpath(embeddedKotlin("gradle-plugin")) } + $repositoriesBlock + } + """, + "" + ) + } + + def "sourcePath includes plugins classpath sources resolved against project"() { + + expect: + assertSourcePathIncludesKotlinPluginSourcesGiven( + "", + """ plugins { kotlin("jvm") version "$targetKotlinVersion" } """ + ) + } + + @LeaksFileHandles("Kotlin compiler daemon on buildSrc jar") + def "sourcePath includes buildSrc source roots"() { + + given: + withKotlinBuildSrc() + withDefaultSettings().append(""" + include(":sub") + """) + + expect: + assertThat( + sourcePathFor(withFile("sub/build.gradle.kts")), + matchesProjectsSourceRoots(withMainSourceSetJavaKotlinIn("buildSrc")) + ) + } + + @LeaksFileHandles("Kotlin compiler daemon on buildSrc jar") + def "sourcePath includes buildSrc project dependencies source roots"() { + + given: + def sourceRoots = withMultiProjectKotlinBuildSrc() + withDefaultSettings().append(""" + include(":sub") + """) + + expect: + assertThat( + sourcePathFor(withFile("sub/build.gradle.kts")), + matchesProjectsSourceRoots(sourceRoots) + ) + } + + private void assertSourcePathIncludesGradleSourcesGiven(String rootProjectScript, String subProjectScript) { + assertSourcePathGiven( + rootProjectScript, + subProjectScript, + hasItems("core-api") + ) + } + + private void assertSourcePathIncludesKotlinStdlibSourcesGiven(String rootProjectScript, String subProjectScript) { + assertSourcePathGiven( + rootProjectScript, + subProjectScript, + hasItems("kotlin-stdlib-jdk8-${targetKotlinVersion}-sources.jar".toString()) + ) + } + + private void assertSourcePathIncludesKotlinPluginSourcesGiven(String rootProjectScript, String subProjectScript) { + assertSourcePathGiven( + rootProjectScript, + subProjectScript, + hasItems( + equalTo("kotlin-gradle-plugin-${targetKotlinVersion}-sources.jar".toString()), + matching("annotations-[0-9.]+-sources\\.jar") + ) + ) + } + + private void assertSourcePathGiven( + String rootProjectScript, + String subProjectScript, + Matcher> matches + ) { + + def subProjectName = "sub" + withDefaultSettings().append(""" + include("$subProjectName") + """) + + withBuildScript(rootProjectScript) + def subProjectScriptFile = withBuildScriptIn(subProjectName, subProjectScript) + + def srcConventionalPathDirNames = ["java", "groovy", "kotlin", "resources"] + def sourcePath = sourcePathFor(subProjectScriptFile).collect { path -> + if (srcConventionalPathDirNames.contains(path.name)) { + path.parentFile.parentFile.parentFile.name + } else { + path.name + } + }.unique() + assertThat(sourcePath, matches) + } + + private static String buildScriptDependencyOn(File jar) { + return """ + buildscript { + dependencies { classpath(files("${normalizedPathOf(jar)}")) } + } + """ + } +} diff --git a/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinInitScriptModelCrossVersionSpec.groovy b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinInitScriptModelCrossVersionSpec.groovy new file mode 100644 index 0000000000000..d8270efacf3f2 --- /dev/null +++ b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinInitScriptModelCrossVersionSpec.groovy @@ -0,0 +1,82 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.tooling.builders.r54 + +import org.gradle.integtests.tooling.fixture.TargetGradleVersion + +import org.gradle.kotlin.dsl.tooling.builders.AbstractKotlinScriptModelCrossVersionTest + +import static org.junit.Assert.assertThat +import static org.hamcrest.CoreMatchers.not + + +@TargetGradleVersion(">=5.4") +class KotlinInitScriptModelCrossVersionSpec extends AbstractKotlinScriptModelCrossVersionTest { + + def "initscript classpath does not include buildSrc"() { + + given: + withBuildSrc() + withDefaultSettings() + + and: + def initScript = withFile("my.init.gradle.kts") + + when: + def classPath = canonicalClassPathFor(initScript) + + then: + assertContainsGradleKotlinDslJars(classPath) + assertThat( + classPath.collect { it.name }, + not(hasBuildSrc()) + ) + } + + def "can fetch initscript classpath in face of compilation errors"() { + + given: + withDefaultSettings() + withEmptyJar("classes.jar") + + and: + def initScript = + withFile("my.init.gradle.kts") << """ + initscript { + dependencies { + classpath(files("classes.jar")) + } + } + + val p = + """ + + when: + def classPath = canonicalClassPathFor(initScript) + + then: + assertContainsGradleKotlinDslJars(classPath) + assertClassPathContains( + classPath, + file("classes.jar") + ) + } + + List canonicalClassPathFor(File initScript) { + canonicalClassPathFor(projectDir, initScript) + } +} diff --git a/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinSettingsScriptModelCrossVersionSpec.groovy b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinSettingsScriptModelCrossVersionSpec.groovy new file mode 100644 index 0000000000000..d3b1d93c4564e --- /dev/null +++ b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/KotlinSettingsScriptModelCrossVersionSpec.groovy @@ -0,0 +1,130 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.tooling.builders.r54 + +import org.gradle.integtests.tooling.fixture.TargetGradleVersion +import org.gradle.test.fixtures.file.LeaksFileHandles + +import org.gradle.kotlin.dsl.tooling.builders.AbstractKotlinScriptModelCrossVersionTest + +import static org.junit.Assert.assertThat + + +@TargetGradleVersion(">=5.4") +class KotlinSettingsScriptModelCrossVersionSpec extends AbstractKotlinScriptModelCrossVersionTest { + + def "can fetch classpath of settings script"() { + + given: + withBuildSrc() + + and: + def settingsDependency = withEmptyJar("settings-dependency.jar") + def settings = withSettings(""" + buildscript { + dependencies { + classpath(files("${normalizedPathOf(settingsDependency)}")) + } + } + """) + + and: + def projectDependency = withEmptyJar("project-dependency.jar") + file("build.gradle") << """ + buildscript { + dependencies { + classpath(files("${normalizedPathOf(projectDependency)}")) + } + } + """ + + when: + def classPath = canonicalClassPathFor(projectDir, settings) + + then: + assertContainsBuildSrc(classPath) + assertContainsGradleKotlinDslJars(classPath) + assertIncludes(classPath, settingsDependency) + assertExcludes(classPath, projectDependency) + } + + def "can fetch classpath of settings script plugin"() { + + given: + withBuildSrc() + withDefaultSettings() + + and: + def settingsDependency = withEmptyJar("settings-dependency.jar") + def settings = withFile("my.settings.gradle.kts", """ + buildscript { + dependencies { + classpath(files("${normalizedPathOf(settingsDependency)}")) + } + } + """) + + and: + def projectDependency = withEmptyJar("project-dependency.jar") + withFile("build.gradle", """ + buildscript { + dependencies { + classpath(files("${normalizedPathOf(projectDependency)}")) + } + } + """) + + when: + def classPath = canonicalClassPathFor(projectDir, settings) + + then: + assertContainsBuildSrc(classPath) + assertContainsGradleKotlinDslJars(classPath) + assertIncludes(classPath, settingsDependency) + assertExcludes(classPath, projectDependency) + } + + @LeaksFileHandles("Kotlin compiler daemon on buildSrc jar") + def "sourcePath includes buildSrc source roots"() { + + given: + withKotlinBuildSrc() + def settings = withDefaultSettings().append(""" + include(":sub") + """) + + expect: + assertThat( + sourcePathFor(settings), + matchesProjectsSourceRoots(withMainSourceSetJavaKotlinIn("buildSrc"))) + } + + @LeaksFileHandles("Kotlin compiler daemon on buildSrc jar") + def "sourcePath includes buildSrc project dependencies source roots"() { + + given: + def sourceRoots = withMultiProjectKotlinBuildSrc() + def settings = withDefaultSettings().append(""" + include(":sub") + """) + + expect: + assertThat( + sourcePathFor(settings), + matchesProjectsSourceRoots(sourceRoots)) + } +} diff --git a/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/PrecompiledScriptPluginModelCrossVersionSpec.groovy b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/PrecompiledScriptPluginModelCrossVersionSpec.groovy new file mode 100644 index 0000000000000..17c54df81f41f --- /dev/null +++ b/subprojects/kotlin-dsl-tooling-builders/src/crossVersionTest/groovy/org/gradle/kotlin/dsl/tooling/builders/r54/PrecompiledScriptPluginModelCrossVersionSpec.groovy @@ -0,0 +1,143 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.tooling.builders.r54 + +import org.gradle.integtests.tooling.fixture.TargetGradleVersion +import org.gradle.test.fixtures.file.LeaksFileHandles +import org.gradle.test.fixtures.file.TestFile + +import org.gradle.kotlin.dsl.tooling.builders.AbstractKotlinScriptModelCrossVersionTest + +import static org.hamcrest.CoreMatchers.hasItems +import static org.hamcrest.CoreMatchers.startsWith +import static org.hamcrest.MatcherAssert.assertThat + +@TargetGradleVersion(">=5.4") +class PrecompiledScriptPluginModelCrossVersionSpec extends AbstractKotlinScriptModelCrossVersionTest { + + @LeaksFileHandles("Kotlin Compiler Daemon working directory") + def "given a single project build, the classpath of a precompiled script plugin is the compile classpath of its enclosing source-set"() { + + given: + def implementationDependency = withEmptyJar("implementation.jar") + def classpathDependency = withEmptyJar("classpath.jar") + + and: + withDefaultSettings() + withBuildScript(""" + plugins { + `kotlin-dsl` + } + + buildscript { + dependencies { + classpath(files("${classpathDependency.name}")) + } + } + + dependencies { + implementation(files("${implementationDependency.name}")) + } + """) + + and: + def precompiledScriptPlugin = withPrecompiledKotlinScript("my-plugin.gradle.kts", "") + + expect: + assertClassPathFor( + precompiledScriptPlugin, + [implementationDependency] as Set, + [classpathDependency] as Set + ) + } + + def "given a multi-project build, the classpath of a precompiled script plugin is the compile classpath of its enclosing source-set"() { + + given: + def dependencyA = withEmptyJar("a.jar") + def dependencyB = withEmptyJar("b.jar") + + and: + withDefaultSettings().append(""" + include("project-a") + include("project-b") + """.stripIndent()) + + and: + withImplementationDependencyOn("project-a", dependencyA) + withFile("project-a/src/main/kotlin/my-plugin-a.gradle.kts") + + and: + withImplementationDependencyOn("project-b", dependencyB) + withFile("project-b/src/main/kotlin/my-plugin-b.gradle.kts") + + expect: + assertClassPathFor( + file("project-a/src/main/kotlin/my-plugin-a.gradle.kts"), + [dependencyA] as Set, + [dependencyB] as Set + ) + + and: + assertClassPathFor( + file("project-b/src/main/kotlin/my-plugin-b.gradle.kts"), + [dependencyB] as Set, + [dependencyA] as Set + ) + } + + def "implicit imports include type-safe accessors packages"() { + + given: + withDefaultSettings() + withBuildScript(""" + plugins { + `kotlin-dsl` + } + """) + + and: + def pluginFile = withPrecompiledKotlinScript("plugin.gradle.kts", """ + plugins { java } + """) + + expect: + assertThat( + kotlinBuildScriptModelFor(projectDir, pluginFile).implicitImports, + hasItems( + startsWith("gradle.kotlin.dsl.accessors._"), + startsWith("gradle.kotlin.dsl.plugins._") + ) + ) + } + + private TestFile withPrecompiledKotlinScript(String fileName, String code) { + return withFile("src/main/kotlin/$fileName", code) + } + + private TestFile withImplementationDependencyOn(String basedir, TestFile jar) { + return withBuildScriptIn(basedir, """ + plugins { + `kotlin-dsl` + } + + dependencies { + implementation(files("${jar.name}")) + } + """) + } +} diff --git a/subprojects/kotlin-dsl-tooling-builders/src/main/kotlin/org/gradle/kotlin/dsl/tooling/builders/KotlinBuildScriptModelBuilder.kt b/subprojects/kotlin-dsl-tooling-builders/src/main/kotlin/org/gradle/kotlin/dsl/tooling/builders/KotlinBuildScriptModelBuilder.kt index d0b43d744d091..d90ec88acd1d3 100644 --- a/subprojects/kotlin-dsl-tooling-builders/src/main/kotlin/org/gradle/kotlin/dsl/tooling/builders/KotlinBuildScriptModelBuilder.kt +++ b/subprojects/kotlin-dsl-tooling-builders/src/main/kotlin/org/gradle/kotlin/dsl/tooling/builders/KotlinBuildScriptModelBuilder.kt @@ -36,12 +36,15 @@ import org.gradle.internal.classpath.DefaultClassPath import org.gradle.internal.resource.BasicTextResourceLoader import org.gradle.internal.time.Time.startTimer +import org.gradle.kotlin.dsl.* import org.gradle.kotlin.dsl.accessors.AccessorsClassPath -import org.gradle.kotlin.dsl.accessors.pluginAccessorsClassPath +import org.gradle.kotlin.dsl.accessors.pluginSpecBuildersClassPath import org.gradle.kotlin.dsl.accessors.projectAccessorsClassPath import org.gradle.kotlin.dsl.execution.EvalOption +import org.gradle.kotlin.dsl.precompile.PrecompiledScriptDependenciesResolver + import org.gradle.kotlin.dsl.provider.ClassPathModeExceptionCollector import org.gradle.kotlin.dsl.provider.KotlinScriptClassPathProvider import org.gradle.kotlin.dsl.provider.KotlinScriptEvaluator @@ -60,7 +63,6 @@ import org.gradle.kotlin.dsl.support.serviceOf import org.gradle.kotlin.dsl.tooling.models.EditorReport import org.gradle.kotlin.dsl.tooling.models.KotlinBuildScriptModel -import org.gradle.kotlin.dsl.typeOf import org.gradle.tooling.provider.model.ToolingModelBuilder @@ -69,7 +71,7 @@ import java.io.PrintWriter import java.io.Serializable import java.io.StringWriter -import java.util.* +import java.util.EnumSet private @@ -203,10 +205,35 @@ fun precompiledScriptPluginModelBuilder( scriptFile = scriptFile, project = modelRequestProject, scriptClassPath = DefaultClassPath.of(enclosingSourceSet.sourceSet.compileClasspath), - enclosingScriptProjectDir = enclosingSourceSet.project.projectDir + enclosingScriptProjectDir = enclosingSourceSet.project.projectDir, + additionalImports = { + enclosingSourceSet.project.precompiledScriptPluginsMetadataDir.run { + implicitImportsFrom( + resolve("accessors").resolve(hashOf(scriptFile)) + ) + implicitImportsFrom( + resolve("plugin-spec-builders").resolve("implicit-imports") + ) + } + } ) +private +val Project.precompiledScriptPluginsMetadataDir: File + get() = buildDir.resolve("kotlin-dsl/precompiled-script-plugins-metadata").apply { + require(isDirectory) + } + + +private +fun implicitImportsFrom(file: File): List = + file.takeIf { it.isFile }?.readLines() ?: emptyList() + + +private +fun hashOf(scriptFile: File) = PrecompiledScriptDependenciesResolver.hashOf(scriptFile.readText()) + + private fun projectScriptModelBuilder( scriptFile: File?, @@ -216,7 +243,7 @@ fun projectScriptModelBuilder( project = project, scriptClassPath = project.scriptCompilationClassPath, accessorsClassPath = { classPath -> - projectAccessorsClassPath(project, classPath) + pluginAccessorsClassPath(project) + projectAccessorsClassPath(project, classPath) + pluginSpecBuildersClassPath(project) }, sourceLookupScriptHandlers = sourceLookupScriptHandlersFor(project), enclosingScriptProjectDir = project.projectDir @@ -359,7 +386,8 @@ data class KotlinScriptTargetModelBuilder( val scriptClassPath: ClassPath, val accessorsClassPath: (ClassPath) -> AccessorsClassPath = { AccessorsClassPath.empty }, val sourceLookupScriptHandlers: List = emptyList(), - val enclosingScriptProjectDir: File? = null + val enclosingScriptProjectDir: File? = null, + val additionalImports: () -> List = { emptyList() } ) { fun buildModel(): KotlinBuildScriptModel { @@ -370,10 +398,15 @@ data class KotlinScriptTargetModelBuilder( accessorsClassPath(scriptClassPath) } ?: AccessorsClassPath.empty + val additionalImports = + classPathModeExceptionCollector.ignoringErrors { + additionalImports() + } ?: emptyList() + return StandardKotlinBuildScriptModel( (scriptClassPath + accessorsClassPath.bin).asFiles, (gradleSource() + classpathSources + accessorsClassPath.src).asFiles, - implicitImports, + implicitImports + additionalImports, buildEditorReportsFor(classPathModeExceptionCollector.exceptions), classPathModeExceptionCollector.exceptions.map(::exceptionToString), enclosingScriptProjectDir diff --git a/subprojects/kotlin-dsl-tooling-builders/src/main/kotlin/org/gradle/kotlin/dsl/tooling/builders/KotlinScriptingModelBuildersRegistrationAction.kt b/subprojects/kotlin-dsl-tooling-builders/src/main/kotlin/org/gradle/kotlin/dsl/tooling/builders/KotlinScriptingModelBuildersRegistrationAction.kt index facfbb5beb659..f02bbb717630f 100644 --- a/subprojects/kotlin-dsl-tooling-builders/src/main/kotlin/org/gradle/kotlin/dsl/tooling/builders/KotlinScriptingModelBuildersRegistrationAction.kt +++ b/subprojects/kotlin-dsl-tooling-builders/src/main/kotlin/org/gradle/kotlin/dsl/tooling/builders/KotlinScriptingModelBuildersRegistrationAction.kt @@ -19,6 +19,7 @@ import org.gradle.api.internal.project.ProjectInternal import org.gradle.configuration.project.ProjectConfigureAction +import org.gradle.kotlin.dsl.resolver.kotlinBuildScriptModelTask import org.gradle.kotlin.dsl.support.serviceOf import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry @@ -27,9 +28,12 @@ import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry class KotlinScriptingModelBuildersRegistrationAction : ProjectConfigureAction { override fun execute(project: ProjectInternal) { - project.serviceOf().run { + project.serviceOf().apply { register(KotlinBuildScriptModelBuilder) register(KotlinBuildScriptTemplateModelBuilder) } + project.tasks.apply { + register(kotlinBuildScriptModelTask) + } } } diff --git a/subprojects/kotlin-dsl/kotlin-dsl.gradle.kts b/subprojects/kotlin-dsl/kotlin-dsl.gradle.kts index ace709535053d..2d249e4b6a32c 100644 --- a/subprojects/kotlin-dsl/kotlin-dsl.gradle.kts +++ b/subprojects/kotlin-dsl/kotlin-dsl.gradle.kts @@ -18,7 +18,6 @@ import org.gradle.gradlebuild.unittestandcompile.ModuleType import build.futureKotlin import build.kotlin import build.kotlinVersion -import build.withCompileOnlyGradleApiModulesWithParameterNames import codegen.GenerateKotlinDependencyExtensions plugins { @@ -31,12 +30,12 @@ gradlebuildJava { moduleType = ModuleType.CORE } -withCompileOnlyGradleApiModulesWithParameterNames(":toolingApi") - dependencies { api(project(":distributionsDependencies")) + compileOnly(project(":toolingApi")) + compile(project(":kotlinDslToolingModels")) compile(project(":kotlinCompilerEmbeddable")) @@ -44,7 +43,7 @@ dependencies { compile(futureKotlin("sam-with-receiver-compiler-plugin")) { isTransitive = false } - compile("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.0.4") { + compile("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.0.5") { isTransitive = false } @@ -70,16 +69,21 @@ dependencies { } // --- Enable automatic generation of API extensions ------------------- -val apiExtensionsOutputDir = file("src/generated/kotlin") +val apiExtensionsOutputDir = layout.buildDirectory.dir("generated-sources/kotlin") -val publishedKotlinDslPluginVersion = "1.2.2" // TODO:kotlin-dsl +val publishedKotlinDslPluginVersion = "1.2.6" // TODO:kotlin-dsl tasks { + // TODO:kotlin-dsl + verifyTestFilesCleanup { + enabled = false + } + val generateKotlinDependencyExtensions by registering(GenerateKotlinDependencyExtensions::class) { - outputFile = apiExtensionsOutputDir.resolve("org/gradle/kotlin/dsl/KotlinDependencyExtensions.kt") - embeddedKotlinVersion = kotlinVersion - kotlinDslPluginsVersion = publishedKotlinDslPluginVersion + outputDir.set(apiExtensionsOutputDir) + embeddedKotlinVersion.set(kotlinVersion) + kotlinDslPluginsVersion.set(publishedKotlinDslPluginVersion) } val generateExtensions by registering { @@ -90,10 +94,6 @@ tasks { kotlin.srcDir(files(apiExtensionsOutputDir).builtBy(generateExtensions)) } - clean { - delete(apiExtensionsOutputDir) - } - // -- Version manifest properties -------------------------------------- val writeVersionsManifest by registering(WriteProperties::class) { outputFile = buildDir.resolve("versionsManifest/gradle-kotlin-dsl-versions.properties") diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/accessors/AccessorsClassPathIntegrationTest.kt b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/accessors/AccessorsClassPathIntegrationTest.kt index 2f56e422a9184..dc8c232c430ca 100644 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/accessors/AccessorsClassPathIntegrationTest.kt +++ b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/accessors/AccessorsClassPathIntegrationTest.kt @@ -16,65 +16,15 @@ package org.gradle.kotlin.dsl.accessors -import org.gradle.kotlin.dsl.fixtures.matching - -import org.gradle.kotlin.dsl.integration.ScriptModelIntegrationTest +import org.gradle.kotlin.dsl.fixtures.AbstractKotlinIntegrationTest import org.hamcrest.CoreMatchers.containsString -import org.hamcrest.CoreMatchers.equalTo -import org.hamcrest.CoreMatchers.hasItem -import org.hamcrest.CoreMatchers.not import org.hamcrest.MatcherAssert.assertThat import org.junit.Test -import java.io.File - - -class AccessorsClassPathIntegrationTest : ScriptModelIntegrationTest() { - - @Test - fun `classpath model includes jit accessors by default`() { - - withDefaultSettings() - val buildFile = withBuildScript(""" - plugins { java } - """) - - assertAccessorsInClassPathOf(buildFile) - } - - @Test - fun `jit accessors can be turned off`() { - - withDefaultSettings() - val buildFile = withBuildScript(""" - plugins { java } - """) - - withFile("gradle.properties", "org.gradle.kotlin.dsl.accessors=off") - - assertThat( - classPathFor(buildFile), - not(hasAccessorsClasses()) - ) - } - - @Test - fun `the set of jit accessors is a function of the set of applied plugins`() { - - // TODO:accessors - rework this test to ensure it's providing enough coverage - val s1 = setOfAutomaticAccessorsFor(setOf("application")) - val s2 = setOfAutomaticAccessorsFor(setOf("java")) - val s3 = setOfAutomaticAccessorsFor(setOf("application")) - val s4 = setOfAutomaticAccessorsFor(setOf("application", "java")) - val s5 = setOfAutomaticAccessorsFor(setOf("java")) - assertThat(s1, not(equalTo(s2))) // application ≠ java - assertThat(s1, equalTo(s3)) // application = application - assertThat(s2, equalTo(s5)) // java = java - assertThat(s1, equalTo(s4)) // application ⊇ java - } +class AccessorsClassPathIntegrationTest : AbstractKotlinIntegrationTest() { @Test fun `warning is emitted if a gradle slash project dash schema dot json file is present`() { @@ -86,55 +36,4 @@ class AccessorsClassPathIntegrationTest : ScriptModelIntegrationTest() { assertThat(build("help").output, containsString(projectSchemaResourceDiscontinuedWarning)) } - - private - fun setOfAutomaticAccessorsFor(plugins: Set): File { - withDefaultSettings() - val script = "plugins {\n${plugins.joinToString(separator = "\n")}\n}" - val buildFile = withBuildScript(script, produceFile = ::newOrExisting) - return accessorsClassFor(buildFile)!!.relativeTo(buildFile.parentFile) - } - - private - fun assertAccessorsInClassPathOf(buildFile: File) { - val model = kotlinBuildScriptModelFor(buildFile) - assertThat(model.classPath, hasAccessorsClasses()) - assertThat(model.sourcePath, hasAccessorsSource()) - } - - private - fun hasAccessorsSource() = - hasItem( - matching({ appendText("accessors source") }) { - resolve(accessorsSourceFilePath).isFile - } - ) - - private - fun hasAccessorsClasses() = - hasItem( - matching({ appendText("accessors classes") }) { - resolve(accessorsClassFilePath).isFile - } - ) - - private - fun accessorsClassFor(buildFile: File) = - classPathFor(buildFile).find { - it.isDirectory && it.resolve(accessorsClassFilePath).isFile - } - - private - val accessorsSourceFilePath = "org/gradle/kotlin/dsl/ArchivesConfigurationAccessors.kt" - - private - val accessorsClassFilePath = "org/gradle/kotlin/dsl/ArchivesConfigurationAccessorsKt.class" - - private - fun classPathFor(buildFile: File) = - kotlinBuildScriptModelFor(buildFile).classPath - - private - fun kotlinBuildScriptModelFor(buildFile: File) = - kotlinBuildScriptModelFor(projectRoot, buildFile) } diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinBuildScriptModelIntegrationTest.kt b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinBuildScriptModelIntegrationTest.kt deleted file mode 100644 index 9a550d0656ea7..0000000000000 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinBuildScriptModelIntegrationTest.kt +++ /dev/null @@ -1,530 +0,0 @@ -package org.gradle.kotlin.dsl.integration - -import org.gradle.kotlin.dsl.embeddedKotlinVersion -import org.gradle.kotlin.dsl.fixtures.DeepThought -import org.gradle.kotlin.dsl.fixtures.matching -import org.gradle.kotlin.dsl.fixtures.normalisedPath - -import org.hamcrest.CoreMatchers.allOf -import org.hamcrest.CoreMatchers.equalTo -import org.hamcrest.CoreMatchers.hasItem -import org.hamcrest.CoreMatchers.hasItems -import org.hamcrest.CoreMatchers.not -import org.hamcrest.Matcher -import org.hamcrest.MatcherAssert.assertThat - -import org.junit.Test - -import java.io.File - - -class KotlinBuildScriptModelIntegrationTest : ScriptModelIntegrationTest() { - - @Test - fun `can fetch buildSrc classpath in face of compilation errors`() { - - withBuildSrc() - - withDefaultSettings() - withBuildScript(""" - val p = - """) - - assertContainsBuildSrc(canonicalClassPath()) - } - - @Test - fun `can fetch buildSrc classpath in face of buildscript exceptions`() { - - withBuildSrc() - - withDefaultSettings() - withBuildScript(""" - buildscript { TODO() } - """) - - assertContainsBuildSrc(canonicalClassPath()) - } - - @Test - fun `can fetch buildscript classpath in face of compilation errors`() { - - withFile("classes.jar") - - withDefaultSettings() - withBuildScript(""" - buildscript { - dependencies { - classpath(files("classes.jar")) - } - } - - val p = - """) - - assertClassPathContains( - existing("classes.jar")) - } - - @Test - fun `can fetch classpath in face of buildSrc test failures`() { - withKotlinBuildSrc() - existing("buildSrc/build.gradle.kts").let { buildSrcScript -> - buildSrcScript.writeText(buildSrcScript.readText() + """ - dependencies { - testImplementation("junit:junit:4.12") - } - """) - } - withFile("buildSrc/src/test/kotlin/FailingTest.kt", """ - class FailingTest { - @org.junit.Test fun test() { - throw Exception("BOOM") - } - } - """) - withDefaultSettings() - - assertContainsBuildSrc(canonicalClassPath()) - } - - @Test - fun `can fetch buildscript classpath of top level Groovy script`() { - - withBuildSrc() - - withFile("classes.jar", "") - - withDefaultSettings() - withFile("build.gradle", """ - buildscript { - dependencies { - classpath(files("classes.jar")) - } - } - """) - - val classPath = canonicalClassPath() - assertThat( - classPath.map { it.name }, - hasItem("classes.jar")) - - assertContainsBuildSrc(classPath) - - assertContainsGradleKotlinDslJars(classPath) - } - - @Test - fun `can fetch buildscript classpath for sub-project script when parent has errors`() { - - withSettings("""include("sub")""") - - withDefaultSettings() - withBuildScript("val p =") - - val jar = withClassJar("libs/jar.jar") - - val subProjectScript = - withFile("sub/build.gradle.kts", """ - buildscript { - dependencies { classpath(files("${jar.normalisedPath}")) } - } - """) - - assertClassPathFor( - subProjectScript, - includes = setOf(jar), - excludes = setOf() - ) - } - - @Test - fun `can fetch buildscript classpath for sub-project script`() { - - assertCanFetchClassPathForSubProjectScriptIn(".") - } - - @Test - fun `can fetch buildscript classpath for sub-project script of nested project`() { - - withDefaultSettings() - - assertCanFetchClassPathForSubProjectScriptIn("nested-project") - } - - private - fun assertCanFetchClassPathForSubProjectScriptIn(location: String) { - withSettingsIn(location, "include(\"foo\", \"bar\")") - - fun withFixture(fixture: String) = - withClassJar("$location/libs/$fixture.jar", DeepThought::class.java) - - val parentJar = withFixture("parent") - val fooJar = withFixture("foo") - val barJar = withFixture("bar") - - val parentBuildScript = "$location/build.gradle".withBuildscriptDependencyOn(parentJar) - val fooBuildScript = "$location/foo/build.gradle.kts".withBuildscriptDependencyOn(fooJar) - val barBuildScript = "$location/bar/build.gradle.kts".withBuildscriptDependencyOn(barJar) - - assertClassPathFor( - parentBuildScript, - includes = setOf(parentJar), - excludes = setOf(fooJar, barJar) - ) - - assertClassPathFor( - fooBuildScript, - includes = setOf(parentJar, fooJar), - excludes = setOf(barJar) - ) - - assertClassPathFor( - barBuildScript, - includes = setOf(parentJar, barJar), - excludes = setOf(fooJar) - ) - } - - @Test - fun `can fetch buildscript classpath for sub-project script outside root project dir`() { - - val rootDependency = withJar("libs/root.jar") - val subDependency = withJar("libs/sub.jar") - - withFolders { - - "root" { - withFile("settings.gradle.kts", """ - include("sub") - project(":sub").apply { - projectDir = file("../sub") - buildFileName = "sub.gradle.kts" - } - """) - } - - "sub" { - } - } - - - val rootBuildScript = "root/build.gradle".withBuildscriptDependencyOn(rootDependency) - val subBuildScript = "sub/sub.gradle.kts".withBuildscriptDependencyOn(subDependency) - val rootProjectDir = rootBuildScript.parentFile - - assertClassPathFor( - rootBuildScript, - includes = setOf(rootDependency), - excludes = setOf(subDependency), - importedProjectDir = rootProjectDir - ) - - assertClassPathFor( - subBuildScript, - includes = setOf(rootDependency, subDependency), - excludes = emptySet(), - importedProjectDir = rootProjectDir - ) - } - - @Test - fun `can fetch buildscript classpath for buildSrc sub-project script outside buildSrc root`() { - - assertCanFetchClassPathForSubProjectScriptOfNestedProjectOutsideProjectRoot("buildSrc") - } - - @Test(expected = AssertionError::class) - fun `can fetch buildscript classpath for sub-project script of nested project outside nested project root`() { - - // This use-case was never supported and continues not to be supported - assertCanFetchClassPathForSubProjectScriptOfNestedProjectOutsideProjectRoot("nested-project") - } - - private - fun assertCanFetchClassPathForSubProjectScriptOfNestedProjectOutsideProjectRoot(nestedProjectName: String) { - withDefaultSettings() - - val rootDependency = withJar("libs/root-dep.jar") - val nestedRootDependency = withJar("libs/$nestedProjectName-root-dep.jar") - val nestedSubDependency = withJar("libs/$nestedProjectName-sub-dep.jar") - - withFolders { - nestedProjectName { - withFile("settings.gradle.kts", """ - include("sub") - project(":sub").apply { - projectDir = file("../$nestedProjectName-sub") - buildFileName = "sub.gradle.kts" - } - """) - } - - "$nestedProjectName-sub" { - } - } - - - val rootBuildScript = "build.gradle".withBuildscriptDependencyOn(rootDependency) - val nestedBuildScript = "$nestedProjectName/build.gradle.kts".withBuildscriptDependencyOn(nestedRootDependency) - val nestedSubBuildScript = "$nestedProjectName-sub/sub.gradle.kts".withBuildscriptDependencyOn(nestedSubDependency) - - assertClassPathFor( - rootBuildScript, - includes = setOf(rootDependency), - excludes = setOf(nestedRootDependency, nestedSubDependency) - ) - - assertClassPathFor( - nestedBuildScript, - includes = setOf(nestedRootDependency), - excludes = setOf(rootDependency, nestedSubDependency) - ) - - assertClassPathFor( - nestedSubBuildScript, - includes = setOf(nestedRootDependency, nestedSubDependency), - excludes = setOf(rootDependency) - ) - } - - private - fun String.withBuildscriptDependencyOn(file: File) = - withFile(this, """ - buildscript { - dependencies { classpath(files("${file.normalisedPath}")) } - } - """) - - @Test - fun `can fetch classpath of script plugin`() { - - assertCanFetchClassPathOfScriptPlugin("") - } - - @Test - fun `can fetch classpath of script plugin with compilation errors`() { - - assertCanFetchClassPathOfScriptPlugin("val p = ") - } - - @Test - fun `can fetch classpath of script plugin with buildscript block compilation errors`() { - - withDefaultSettings() - assertCanFetchClassPathOfScriptPlugin("buildscript { val p = }") - } - - private - fun assertCanFetchClassPathOfScriptPlugin(scriptPluginCode: String) { - withBuildSrc() - - val buildSrcDependency = - withFile("buildSrc-dependency.jar") - - withFile("buildSrc/build.gradle", """ - dependencies { compile(files("../${buildSrcDependency.name}")) } - """) - - val rootProjectDependency = withFile("rootProject-dependency.jar") - - withDefaultSettings() - withFile("build.gradle", """ - buildscript { - dependencies { classpath(files("${rootProjectDependency.name}")) } - } - """) - - val scriptPlugin = withFile("plugin.gradle.kts", scriptPluginCode) - - val scriptPluginClassPath = canonicalClassPathFor(projectRoot, scriptPlugin) - assertThat( - scriptPluginClassPath.map { it.name }, - allOf( - not(hasItem(rootProjectDependency.name)), - hasItem(buildSrcDependency.name) - ) - ) - assertContainsBuildSrc(scriptPluginClassPath) - assertContainsGradleKotlinDslJars(scriptPluginClassPath) - } - - @Test - fun `can fetch classpath of script plugin with buildscript block`() { - - withDefaultSettings() - - val scriptPluginDependency = - withFile("script-plugin-dependency.jar") - - val scriptPlugin = withFile("plugin.gradle.kts", """ - buildscript { - dependencies { classpath(files("${scriptPluginDependency.name}")) } - } - - // Shouldn't be evaluated - throw IllegalStateException() - """) - - val model = kotlinBuildScriptModelFor(projectRoot, scriptPlugin) - assertThat( - "Script body shouldn't be evaluated", - model.exceptions, - equalTo(emptyList())) - - val scriptPluginClassPath = model.canonicalClassPath - assertThat( - scriptPluginClassPath.map { it.name }, - hasItem(scriptPluginDependency.name)) - - assertContainsGradleKotlinDslJars(scriptPluginClassPath) - } - - @Test - fun `can fetch classpath of plugin portal plugin in plugins block`() { - withDefaultSettings() - withBuildScript(""" - plugins { - id("org.gradle.hello-world") version "0.2" - } - """) - - assertThat( - canonicalClassPath().map { it.name }, - hasItems("gradle-hello-world-plugin-0.2.jar")) - } - - @Test - fun `sourcePath includes Gradle sources`() { - - assertSourcePathIncludesGradleSourcesGiven( - rootProjectScript = "", - subProjectScript = "") - } - - @Test - fun `sourcePath includes kotlin-stdlib sources resolved against project`() { - - assertSourcePathIncludesKotlinStdlibSourcesGiven( - rootProjectScript = "", - subProjectScript = "buildscript { $repositoriesBlock }") - } - - @Test - fun `sourcePath includes kotlin-stdlib sources resolved against project hierarchy`() { - - assertSourcePathIncludesKotlinStdlibSourcesGiven( - rootProjectScript = "buildscript { $repositoriesBlock }", - subProjectScript = "") - } - - @Test - fun `sourcePath includes buildscript classpath sources resolved against project`() { - - assertSourcePathIncludesKotlinPluginSourcesGiven( - rootProjectScript = "", - subProjectScript = """ - buildscript { - dependencies { classpath(embeddedKotlin("gradle-plugin")) } - $repositoriesBlock - } - """) - } - - @Test - fun `sourcePath includes buildscript classpath sources resolved against project hierarchy`() { - - assertSourcePathIncludesKotlinPluginSourcesGiven( - rootProjectScript = """ - buildscript { - dependencies { classpath(embeddedKotlin("gradle-plugin")) } - $repositoriesBlock - } - """, - subProjectScript = "") - } - - @Test - fun `sourcePath includes plugins classpath sources resolved against project`() { - - assertSourcePathIncludesKotlinPluginSourcesGiven( - rootProjectScript = "", - subProjectScript = """ plugins { kotlin("jvm") version "$embeddedKotlinVersion" } """) - } - - @Test - fun `sourcePath includes buildSrc source roots`() { - - withKotlinBuildSrc() - withSettings("""include(":sub")""") - - assertThat( - sourcePathFor(withFile("sub/build.gradle.kts")), - matchesProjectsSourceRoots(withMainSourceSetJavaKotlinIn("buildSrc"))) - } - - @Test - fun `sourcePath includes buildSrc project dependencies source roots`() { - - val sourceRoots = withMultiProjectKotlinBuildSrc() - withSettings("""include(":sub")""") - - assertThat( - sourcePathFor(withFile("sub/build.gradle.kts")), - matchesProjectsSourceRoots(*sourceRoots)) - } - - private - fun assertSourcePathIncludesGradleSourcesGiven(rootProjectScript: String, subProjectScript: String) { - - assertSourcePathGiven( - rootProjectScript, - subProjectScript, - hasItems("core-api")) - } - - private - fun assertSourcePathIncludesKotlinStdlibSourcesGiven(rootProjectScript: String, subProjectScript: String) { - - assertSourcePathGiven( - rootProjectScript, - subProjectScript, - hasItems("kotlin-stdlib-jdk8-$embeddedKotlinVersion-sources.jar")) - } - - private - fun assertSourcePathIncludesKotlinPluginSourcesGiven(rootProjectScript: String, subProjectScript: String) { - - assertSourcePathGiven( - rootProjectScript, - subProjectScript, - hasItems( - equalTo("kotlin-gradle-plugin-$embeddedKotlinVersion-sources.jar"), - matching("annotations-[0-9.]+-sources\\.jar"))) - } - - private - fun assertSourcePathGiven( - rootProjectScript: String, - subProjectScript: String, - matches: Matcher> - ) { - - val subProjectName = "sub" - withSettings(""" - include("$subProjectName") - """) - - withBuildScript(rootProjectScript) - val subProjectScriptFile = withBuildScriptIn(subProjectName, subProjectScript) - - val srcConventionalPathDirNames = listOf("java", "groovy", "kotlin", "resources") - val sourcePath = sourcePathFor(subProjectScriptFile).map { path -> - when { - srcConventionalPathDirNames.contains(path.name) -> path.parentFile.parentFile.parentFile.name - else -> path.name - } - }.distinct() - assertThat(sourcePath, matches) - } -} diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinInitScriptModelIntegrationTest.kt b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinInitScriptModelIntegrationTest.kt deleted file mode 100644 index a6e35528493b3..0000000000000 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinInitScriptModelIntegrationTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -package org.gradle.kotlin.dsl.integration - -import org.hamcrest.CoreMatchers.not -import org.hamcrest.MatcherAssert.assertThat - -import org.junit.Test - -import java.io.File - - -class KotlinInitScriptModelIntegrationTest : ScriptModelIntegrationTest() { - - @Test - fun `initscript classpath does not include buildSrc`() { - - withBuildSrc() - withDefaultSettings() - - val initScript = withFile("my.init.gradle.kts") - val classPath = canonicalClassPathFor(initScript) - - assertContainsGradleKotlinDslJars(classPath) - assertThat( - classPath.map { it.name }, - not(hasBuildSrc())) - } - - @Test - fun `can fetch initscript classpath in face of compilation errors`() { - - withDefaultSettings() - withFile("classes.jar") - - val initScript = - withFile("my.init.gradle.kts", """ - initscript { - dependencies { - classpath(files("classes.jar")) - } - } - - val p = - """) - - val classPath = canonicalClassPathFor(initScript) - - assertContainsGradleKotlinDslJars(classPath) - assertClassPathContains( - classPath, - existing("classes.jar")) - } - - private - fun canonicalClassPathFor(initScript: File) = - canonicalClassPathFor(projectRoot, initScript) -} diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinSettingsScriptModelIntegrationTest.kt b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinSettingsScriptModelIntegrationTest.kt deleted file mode 100644 index fbf2f6409ce3f..0000000000000 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/KotlinSettingsScriptModelIntegrationTest.kt +++ /dev/null @@ -1,96 +0,0 @@ -package org.gradle.kotlin.dsl.integration - -import org.gradle.kotlin.dsl.fixtures.normalisedPath - -import org.junit.Assert.assertThat - -import org.junit.Test - - -class KotlinSettingsScriptModelIntegrationTest : ScriptModelIntegrationTest() { - - @Test - fun `can fetch classpath of settings script`() { - - withBuildSrc() - - val settingsDependency = withFile("settings-dependency.jar", "") - val settings = withSettings(""" - buildscript { - dependencies { - classpath(files("${settingsDependency.normalisedPath}")) - } - } - """) - - val projectDependency = withFile("project-dependency.jar", "") - withFile("build.gradle", """ - buildscript { - dependencies { - classpath(files("${projectDependency.normalisedPath}")) - } - } - """) - - val classPath = canonicalClassPathFor(projectRoot, settings) - - assertContainsBuildSrc(classPath) - assertContainsGradleKotlinDslJars(classPath) - assertIncludes(classPath, settingsDependency) - assertExcludes(classPath, projectDependency) - } - - @Test - fun `can fetch classpath of settings script plugin`() { - - withBuildSrc() - withDefaultSettings() - - val settingsDependency = withFile("settings-dependency.jar", "") - val settings = withFile("my.settings.gradle.kts", """ - buildscript { - dependencies { - classpath(files("${settingsDependency.normalisedPath}")) - } - } - """) - - val projectDependency = withFile("project-dependency.jar", "") - withFile("build.gradle", """ - buildscript { - dependencies { - classpath(files("${projectDependency.normalisedPath}")) - } - } - """) - - val classPath = canonicalClassPathFor(projectRoot, settings) - - assertContainsBuildSrc(classPath) - assertContainsGradleKotlinDslJars(classPath) - assertIncludes(classPath, settingsDependency) - assertExcludes(classPath, projectDependency) - } - - @Test - fun `sourcePath includes buildSrc source roots`() { - - withKotlinBuildSrc() - val settings = withSettings("""include(":sub")""") - - assertThat( - sourcePathFor(settings), - matchesProjectsSourceRoots(withMainSourceSetJavaKotlinIn("buildSrc"))) - } - - @Test - fun `sourcePath includes buildSrc project dependencies source roots`() { - - val sourceRoots = withMultiProjectKotlinBuildSrc() - val settings = withSettings("""include(":sub")""") - - assertThat( - sourcePathFor(settings), - matchesProjectsSourceRoots(*sourceRoots)) - } -} diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/PrecompiledScriptPluginModelIntegrationTest.kt b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/PrecompiledScriptPluginModelIntegrationTest.kt deleted file mode 100644 index 0f95dac599fe5..0000000000000 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/PrecompiledScriptPluginModelIntegrationTest.kt +++ /dev/null @@ -1,102 +0,0 @@ -package org.gradle.kotlin.dsl.integration - -import org.gradle.kotlin.dsl.fixtures.FoldersDsl - -import org.junit.Test - -import java.io.File - - -class PrecompiledScriptPluginModelIntegrationTest : ScriptModelIntegrationTest() { - - @Test - fun `given a single project build, the classpath of a precompiled script plugin is the compile classpath of its enclosing source-set`() { - - val implementationDependency = - withJar("implementation.jar") - - val classpathDependency = - withJar("classpath.jar") - - withDefaultSettings() - - withBuildScript(""" - plugins { - `kotlin-dsl` - } - - buildscript { - dependencies { - classpath(files("${classpathDependency.name}")) - } - } - - dependencies { - implementation(files("${implementationDependency.name}")) - } - """) - - val precompiledScriptPlugin = - withFile("src/main/kotlin/my-plugin.gradle.kts") - - assertClassPathFor( - precompiledScriptPlugin, - includes = setOf(implementationDependency), - excludes = setOf(classpathDependency)) - } - - @Test - fun `given a multi-project build, the classpath of a precompiled script plugin is the compile classpath of its enclosing source-set`() { - - val dependencyA = - withFile("a.jar") - - val dependencyB = - withFile("b.jar") - - withFolders { - - withFile("settings.gradle.kts", """ - include("project-a") - include("project-b") - """) - - "project-a" { - "src/main/kotlin" { - withFile("my-plugin-a.gradle.kts") - } - withImplementationDependencyOn(dependencyA) - } - - "project-b" { - "src/main/kotlin" { - withFile("my-plugin-b.gradle.kts") - } - withImplementationDependencyOn(dependencyB) - } - } - - assertClassPathFor( - existing("project-a/src/main/kotlin/my-plugin-a.gradle.kts"), - includes = setOf(dependencyA), - excludes = setOf(dependencyB)) - - assertClassPathFor( - existing("project-b/src/main/kotlin/my-plugin-b.gradle.kts"), - includes = setOf(dependencyB), - excludes = setOf(dependencyA)) - } - - private - fun FoldersDsl.withImplementationDependencyOn(file: File) { - withFile("build.gradle.kts", """ - plugins { - `kotlin-dsl` - } - - dependencies { - implementation(files("${file.name}")) - } - """) - } -} diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/ScriptModelIntegrationTest.kt b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/ScriptModelIntegrationTest.kt deleted file mode 100644 index eb870b4cf8f3b..0000000000000 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/ScriptModelIntegrationTest.kt +++ /dev/null @@ -1,209 +0,0 @@ -package org.gradle.kotlin.dsl.integration - -import org.gradle.kotlin.dsl.concurrent.future - -import org.gradle.kotlin.dsl.fixtures.AbstractKotlinIntegrationTest -import org.gradle.kotlin.dsl.fixtures.DeepThought -import org.gradle.kotlin.dsl.fixtures.matching - -import org.gradle.kotlin.dsl.resolver.GradleInstallation -import org.gradle.kotlin.dsl.resolver.KotlinBuildScriptModelRequest -import org.gradle.kotlin.dsl.resolver.fetchKotlinBuildScriptModelFor - -import org.gradle.kotlin.dsl.tooling.models.KotlinBuildScriptModel - -import org.hamcrest.CoreMatchers.* - -import org.hamcrest.Matcher -import org.hamcrest.MatcherAssert.assertThat - -import java.io.File - - -/** - * Base class for [KotlinBuildScriptModel] integration tests. - */ -abstract class ScriptModelIntegrationTest : AbstractKotlinIntegrationTest() { - - protected - fun sourcePathFor(scriptFile: File) = - kotlinBuildScriptModelFor(projectRoot, scriptFile).sourcePath - - protected - class ProjectSourceRoots(val projectDir: File, val sourceSets: List, val languages: List) - - protected - fun withMainSourceSetJavaIn(projectDir: String) = - ProjectSourceRoots(existing(projectDir), listOf("main"), listOf("java")) - - protected - fun withMainSourceSetJavaKotlinIn(projectDir: String) = - ProjectSourceRoots(existing(projectDir), listOf("main"), listOf("java", "kotlin")) - - protected - fun matchesProjectsSourceRoots(vararg projectSourceRoots: ProjectSourceRoots): Matcher> { - - fun hasLanguageDir(base: File, set: String, lang: String): Matcher> = - hasItem(base.resolve("src/$set/$lang")) - - return allOf( - *projectSourceRoots - .filter { it.languages.isNotEmpty() } - .flatMap { sourceRoots -> - val languageDirs = - sourceRoots.sourceSets.flatMap { sourceSet -> - listOf("java", "kotlin").map { language -> - val hasLanguageDir = hasLanguageDir(sourceRoots.projectDir, sourceSet, language) - if (language in sourceRoots.languages) hasLanguageDir - else not(hasLanguageDir) - } - } - - val resourceDirs = - sourceRoots.sourceSets.map { sourceSet -> - hasLanguageDir(sourceRoots.projectDir, sourceSet, "resources") - } - - languageDirs + resourceDirs - }.toTypedArray()) - } - - protected - fun withMultiProjectKotlinBuildSrc(): Array { - withSettingsIn("buildSrc", """ - include(":a", ":b", ":c") - """) - withFile("buildSrc/build.gradle.kts", """ - plugins { - java - `kotlin-dsl` apply false - } - - val kotlinDslProjects = listOf(project.project(":a"), project.project(":b")) - - kotlinDslProjects.forEach { - it.apply(plugin = "org.gradle.kotlin.kotlin-dsl") - } - - dependencies { - kotlinDslProjects.forEach { - "runtime"(project(it.path)) - } - } - """) - withFile("buildSrc/b/build.gradle.kts", """dependencies { implementation(project(":c")) }""") - withFile("buildSrc/c/build.gradle.kts", "plugins { java }") - - return arrayOf( - withMainSourceSetJavaIn("buildSrc"), - withMainSourceSetJavaKotlinIn("buildSrc/a"), - withMainSourceSetJavaKotlinIn("buildSrc/b"), - withMainSourceSetJavaIn("buildSrc/c")) - } - - protected - fun assertContainsGradleKotlinDslJars(classPath: List) { - val version = "[0-9.]+(-.+?)?" - assertThat( - classPath.map { it.name }, - hasItems( - matching("gradle-kotlin-dsl-$version\\.jar"), - matching("gradle-api-$version\\.jar"), - matching("gradle-kotlin-dsl-extensions-$version\\.jar"))) - } - - protected - fun assertClassPathFor( - buildScript: File, - includes: Set, - excludes: Set, - importedProjectDir: File = projectRoot - ) { - val includeItems = hasItems(*includes.map { it.name }.toTypedArray()) - val excludeItems = not(hasItems(*excludes.map { it.name }.toTypedArray())) - val condition = if (excludes.isEmpty()) includeItems else allOf(includeItems, excludeItems) - assertThat( - classPathFor(importedProjectDir, buildScript).map { it.name }, - condition - ) - } - - protected - fun assertClassPathContains(vararg files: File) = - assertClassPathContains(canonicalClassPath(), *files) - - protected - fun assertClassPathContains(classPath: List, vararg files: File) = - assertThat( - classPath.map { it.name }, - hasItems(*fileNameSetOf(*files))) - - protected - fun assertContainsBuildSrc(classPath: List) = - assertThat( - classPath.map { it.name }, - hasBuildSrc()) - - protected - fun hasBuildSrc() = - hasItem("buildSrc.jar") - - protected - fun assertIncludes(classPath: List, vararg files: File) = - assertThat( - classPath.map { it.name }, - hasItems(*fileNameSetOf(*files))) - - protected - fun assertExcludes(classPath: List, vararg files: File) = - assertThat( - classPath.map { it.name }, - not(hasItems(*fileNameSetOf(*files)))) - - private - fun fileNameSetOf(vararg files: File) = - files.map { it.name }.toSet().toTypedArray().also { - assert(it.size == files.size) - } - - protected - fun canonicalClassPath() = - canonicalClassPathFor(projectRoot) - - protected - fun withJar(named: String): File = - withClassJar(named, DeepThought::class.java) - - internal - fun canonicalClassPathFor(projectDir: File, scriptFile: File? = null) = - kotlinBuildScriptModelFor(projectDir, scriptFile).canonicalClassPath - - private - fun classPathFor(importedProjectDir: File, scriptFile: File?) = - kotlinBuildScriptModelFor(importedProjectDir, scriptFile).classPath - - internal - val KotlinBuildScriptModel.canonicalClassPath - get() = classPath.map(File::getCanonicalFile) - - internal - fun kotlinBuildScriptModelFor(importedProjectDir: File, scriptFile: File? = null): KotlinBuildScriptModel = - future { - fetchKotlinBuildScriptModelFor( - KotlinBuildScriptModelRequest( - projectDir = importedProjectDir, - scriptFile = scriptFile, - gradleInstallation = testGradleInstallation(), - gradleUserHome = buildContext.gradleUserHomeDir - ) - ) { - - setStandardOutput(System.out) - setStandardError(System.err) - } - }.get() - - private - fun testGradleInstallation() = - GradleInstallation.Local(distribution.gradleHomeDir) -} diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/TestKitIntegrationTest.kt b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/TestKitIntegrationTest.kt index b409eefe9dbbb..fa404a88c9cbd 100644 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/TestKitIntegrationTest.kt +++ b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/integration/TestKitIntegrationTest.kt @@ -18,12 +18,15 @@ package org.gradle.kotlin.dsl.integration import org.gradle.kotlin.dsl.fixtures.AbstractKotlinIntegrationTest +import org.gradle.test.fixtures.file.LeaksFileHandles + import org.junit.Test class TestKitIntegrationTest : AbstractKotlinIntegrationTest() { @Test + @LeaksFileHandles("Kotlin Compiler Daemon working directory") fun `withPluginClasspath works`() { requireGradleDistributionOnEmbeddedExecuter() diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/resolver/KotlinScriptDependenciesResolverTest.kt b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/resolver/KotlinScriptDependenciesResolverTest.kt index c536229009514..19903fc6ae883 100644 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/resolver/KotlinScriptDependenciesResolverTest.kt +++ b/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/resolver/KotlinScriptDependenciesResolverTest.kt @@ -31,7 +31,7 @@ import org.hamcrest.Matcher import org.junit.Assert.assertSame import org.junit.Assert.assertThat import org.junit.Assert.assertTrue - +import org.junit.Before import org.junit.Test import java.io.File @@ -46,17 +46,33 @@ import kotlin.script.dependencies.ScriptDependenciesResolver.ReportSeverity class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { + @Before + fun setUpSettings() { + + withDefaultSettings() + } + @Test fun `succeeds with no script`() { - withDefaultSettings() assertSucceeds() } + @Test + fun `returns given Java home`() { + + val javaHome = System.getProperty("java.home") + val env = arrayOf("gradleJavaHome" to javaHome) + assertThat( + resolvedScriptDependencies(env = *env)?.javaHome, + equalTo(javaHome) + ) + } + + @Test fun `succeeds on init script`() { - withDefaultSettings() assertSucceeds(withFile("my.init.gradle.kts", """ require(this is Gradle) """)) @@ -79,7 +95,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { @Test fun `succeeds on project script`() { - withDefaultSettings() assertSucceeds(withFile("build.gradle.kts", """ require(this is Project) """)) @@ -96,8 +111,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { withKotlinBuildSrc() - withDefaultSettings() - assertSucceeds(withFile("buildSrc/src/main/kotlin/my-plugin.init.gradle.kts", """ require(this is Gradle) """)) @@ -108,7 +121,7 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { withKotlinBuildSrc() - withSettings(""" + withDefaultSettings().appendText(""" apply(plugin = "my-plugin") """) @@ -122,7 +135,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { withKotlinBuildSrc() - withDefaultSettings() withBuildScript(""" plugins { id("my-plugin") @@ -138,7 +150,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { fun `report file fatality on TAPI failure`() { // thus disabling syntax highlighting - withDefaultSettings() val editedScript = withBuildScript("") val wrongEnv = arrayOf("gradleHome" to existing("absent")) @@ -155,7 +166,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { @Test fun `report file error on TAPI failure when reusing previous dependencies`() { - withDefaultSettings() val editedScript = withBuildScript("") val previous = resolvedScriptDependencies(editedScript).apply { @@ -182,7 +192,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { BOOM """) - withDefaultSettings() val editedScript = withBuildScript("") resolvedScriptDependencies(editedScript).apply { @@ -201,7 +210,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { withKotlinBuildSrc() val buildSrcKotlinSource = withFile("buildSrc/src/main/kotlin/Foo.kt", "") - withDefaultSettings() val editedScript = withBuildScript("") val previous = resolvedScriptDependencies(editedScript).apply { @@ -224,7 +232,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { fun `do not report file warning on script compilation failure in currently edited script`() { // because the IDE already provides user feedback for those - withDefaultSettings() val editedScript = withBuildScript(""" doNotExists() """) @@ -242,7 +249,7 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { @Test fun `report file warning on script compilation failure in another script`() { - withSettings(""" + withDefaultSettings().appendText(""" include("a", "b") """) withBuildScript("") @@ -263,7 +270,7 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { @Test fun `report file warning on runtime failure in currently edited script`() { - withDefaultSettings() + val editedScript = withBuildScript(""" configurations.getByName("doNotExists") """) @@ -281,7 +288,6 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { @Test fun `report line warning on runtime failure in currently edited script when location aware hints are enabled`() { - withDefaultSettings() withFile("gradle.properties", """ ${EditorReports.locationAwareEditorHintsPropertyName}=true """) @@ -302,7 +308,7 @@ class KotlinScriptDependenciesResolverTest : AbstractKotlinIntegrationTest() { @Test fun `report file warning on runtime failure in another script`() { - withSettings(""" + withDefaultSettings().appendText(""" include("a", "b") """) withBuildScript("") diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ArtifactHandlerExtensions.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ArtifactHandlerExtensions.kt index b12da26d58134..97222505c2dae 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ArtifactHandlerExtensions.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ArtifactHandlerExtensions.kt @@ -22,6 +22,8 @@ import org.gradle.api.artifacts.dsl.ArtifactHandler /** * Configures the published artifacts for this project. + * + * @since 5.1 */ @Incubating operator fun ArtifactHandler.invoke(configuration: ArtifactHandlerScope.() -> Unit): Unit = diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ArtifactHandlerScope.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ArtifactHandlerScope.kt index ff5ecb388edc6..3bc5df2966266 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ArtifactHandlerScope.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ArtifactHandlerScope.kt @@ -21,6 +21,8 @@ import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.PublishArtifact import org.gradle.api.artifacts.dsl.ArtifactHandler +import org.gradle.kotlin.dsl.support.delegates.ArtifactHandlerDelegate + /** * Receiver for `artifacts` block providing convenient utilities for configuring artifacts. @@ -30,7 +32,7 @@ import org.gradle.api.artifacts.dsl.ArtifactHandler class ArtifactHandlerScope private constructor( val artifacts: ArtifactHandler -) : ArtifactHandler by artifacts { +) : ArtifactHandlerDelegate() { companion object { /** @@ -41,6 +43,9 @@ private constructor( ArtifactHandlerScope(artifacts) } + override val delegate: ArtifactHandler + get() = artifacts + /** * Adds an artifact to the given configuration. * diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyConstraintHandlerExtensions.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyConstraintHandlerExtensions.kt index 3a189a5c6b385..2913919346514 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyConstraintHandlerExtensions.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyConstraintHandlerExtensions.kt @@ -22,6 +22,8 @@ import org.gradle.api.artifacts.dsl.DependencyConstraintHandler /** * Configures the dependency constraints. + * + * @since 5.0 */ @Incubating operator fun DependencyConstraintHandler.invoke(configuration: DependencyConstraintHandlerScope.() -> Unit) = diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyConstraintHandlerScope.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyConstraintHandlerScope.kt index b937caaac92f4..f934a4f9afd06 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyConstraintHandlerScope.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyConstraintHandlerScope.kt @@ -21,23 +21,29 @@ import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.DependencyConstraint import org.gradle.api.artifacts.dsl.DependencyConstraintHandler +import org.gradle.kotlin.dsl.support.delegates.DependencyConstraintHandlerDelegate + /** * Receiver for `dependencies.constraints` block providing convenient utilities for configuring dependency constraints. * * @see [DependencyConstraintHandler] + * @since 5.0 */ @Incubating class DependencyConstraintHandlerScope private constructor( val constraints: DependencyConstraintHandler -) : DependencyConstraintHandler by constraints { +) : DependencyConstraintHandlerDelegate() { companion object { fun of(constraints: DependencyConstraintHandler) = DependencyConstraintHandlerScope(constraints) } + override val delegate: DependencyConstraintHandler + get() = constraints + /** * Adds a dependency constraint to the given configuration. * diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyHandlerExtensions.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyHandlerExtensions.kt index 2a5856784a3c0..84a107b333089 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyHandlerExtensions.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyHandlerExtensions.kt @@ -26,6 +26,7 @@ import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.kotlin.dsl.accessors.runtime.externalModuleDependencyFor +import org.gradle.kotlin.dsl.support.delegates.ClientModuleDelegate import org.gradle.kotlin.dsl.support.excludeMapFor import org.gradle.kotlin.dsl.support.mapOfNonNullValuesOf import org.gradle.kotlin.dsl.support.uncheckedCast @@ -163,7 +164,10 @@ inline fun DependencyHandler.configureClientModule( class ClientModuleScope( private val dependencyHandler: DependencyHandler, val clientModule: ClientModule -) : ClientModule by clientModule { +) : ClientModuleDelegate() { + + override val delegate: ClientModule + get() = clientModule fun module( group: String, diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyHandlerScope.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyHandlerScope.kt index 99784b5216198..e115b67d8dc35 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyHandlerScope.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/DependencyHandlerScope.kt @@ -23,6 +23,8 @@ import org.gradle.api.artifacts.ModuleDependency import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.plugins.ExtensionAware +import org.gradle.kotlin.dsl.support.delegates.DependencyHandlerDelegate + /** * Receiver for `dependencies` block providing convenient utilities for configuring dependencies. @@ -32,7 +34,7 @@ import org.gradle.api.plugins.ExtensionAware open class DependencyHandlerScope private constructor( val dependencies: DependencyHandler -) : DependencyHandler by dependencies { +) : DependencyHandlerDelegate() { companion object { fun of(dependencies: DependencyHandler): DependencyHandlerScope = @@ -44,6 +46,9 @@ private constructor( ) : DependencyHandlerScope(dependencies), ExtensionAware by (dependencies as ExtensionAware) } + override val delegate: DependencyHandler + get() = dependencies + /** * Adds a dependency to the given configuration. * diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ExtensionContainerExtensions.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ExtensionContainerExtensions.kt index 5f3ced2369887..bfee77f897565 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ExtensionContainerExtensions.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ExtensionContainerExtensions.kt @@ -73,6 +73,7 @@ inline operator fun ExtensionContainer.getValue(thisRef: Any?, * @throws IllegalArgumentException When an extension with the given name already exists. * * @see [ExtensionContainer.add] + * @since 5.0 */ @Incubating @Suppress("extension_shadowed_by_member") @@ -90,6 +91,7 @@ inline fun ExtensionContainer.add(name: String, extension: T) * @return the created instance * * @see [ExtensionContainer.create] + * @since 5.0 */ @Incubating inline fun ExtensionContainer.create(name: String, vararg constructionArguments: Any): T = @@ -104,6 +106,7 @@ inline fun ExtensionContainer.create(name: String, vararg cons * @throws UnknownDomainObjectException when no matching extension can be found * * @see [ExtensionContainer.getByType] + * @since 5.0 */ @Incubating inline fun ExtensionContainer.getByType(): T = @@ -117,6 +120,7 @@ inline fun ExtensionContainer.getByType(): T = * @return the extension or null if not found * * @see [ExtensionContainer.findByType] + * @since 5.0 */ @Incubating inline fun ExtensionContainer.findByType(): T? = @@ -130,6 +134,7 @@ inline fun ExtensionContainer.findByType(): T? = * @param action the configuration action * * @see [ExtensionContainer.configure] + * @since 5.0 */ @Incubating inline fun ExtensionContainer.configure(noinline action: T.() -> Unit) { diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinBuildScript.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinBuildScript.kt index 8aa3a34835e41..a99b8f6151f0d 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinBuildScript.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinBuildScript.kt @@ -21,6 +21,7 @@ import org.gradle.api.initialization.dsl.ScriptHandler import org.gradle.kotlin.dsl.resolver.KotlinBuildScriptDependenciesResolver import org.gradle.kotlin.dsl.support.KotlinScriptHost +import org.gradle.kotlin.dsl.support.delegates.ProjectDelegate import org.gradle.kotlin.dsl.support.internalError import org.gradle.plugin.use.PluginDependenciesSpec @@ -55,7 +56,10 @@ annotation class KotlinScriptTemplate @GradleDsl abstract class KotlinBuildScript( private val host: KotlinScriptHost -) : Project by host.target { +) : ProjectDelegate() { + + override val delegate: Project + get() = host.target /** * The [ScriptHandler] for this script. diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinInitScript.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinInitScript.kt index 1609b0bc4980b..bbbed2d64d4ce 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinInitScript.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinInitScript.kt @@ -37,6 +37,7 @@ import org.gradle.api.tasks.WorkResult import org.gradle.kotlin.dsl.resolver.KotlinBuildScriptDependenciesResolver import org.gradle.kotlin.dsl.support.KotlinScriptHost +import org.gradle.kotlin.dsl.support.delegates.GradleDelegate import org.gradle.kotlin.dsl.support.internalError import org.gradle.kotlin.dsl.support.serviceOf import org.gradle.kotlin.dsl.support.unsafeLazy @@ -100,7 +101,9 @@ abstract class KotlinInitScript( * Standard implementation of the API exposed to all types of [Gradle] scripts, * precompiled and otherwise. */ -abstract class InitScriptApi(target: Gradle) : Gradle by target { +abstract class InitScriptApi( + override val delegate: Gradle +) : GradleDelegate() { protected abstract val fileOperations: FileOperations diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinSettingsScript.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinSettingsScript.kt index e2a9d8c79bec0..ac149d911a6ed 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinSettingsScript.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/KotlinSettingsScript.kt @@ -49,6 +49,7 @@ import org.gradle.internal.time.Clock import org.gradle.kotlin.dsl.resolver.KotlinBuildScriptDependenciesResolver import org.gradle.kotlin.dsl.support.KotlinScriptHost +import org.gradle.kotlin.dsl.support.delegates.SettingsDelegate import org.gradle.kotlin.dsl.support.get import org.gradle.kotlin.dsl.support.internalError import org.gradle.kotlin.dsl.support.serviceOf @@ -105,7 +106,9 @@ abstract class KotlinSettingsScript( * Standard implementation of the API exposed to all types of [Settings] scripts, * precompiled and otherwise. */ -abstract class SettingsScriptApi(settings: Settings) : Settings by settings { +abstract class SettingsScriptApi( + override val delegate: Settings +) : SettingsDelegate() { protected abstract val fileOperations: FileOperations diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/NamedDomainObjectContainerExtensions.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/NamedDomainObjectContainerExtensions.kt index 3c295e5918811..61fb88c92e368 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/NamedDomainObjectContainerExtensions.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/NamedDomainObjectContainerExtensions.kt @@ -21,6 +21,8 @@ import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.PolymorphicDomainObjectContainer +import org.gradle.kotlin.dsl.support.delegates.NamedDomainObjectContainerDelegate + import kotlin.reflect.KClass import kotlin.reflect.KProperty @@ -219,8 +221,8 @@ private constructor( */ class NamedDomainObjectContainerScope private constructor( - private val container: NamedDomainObjectContainer -) : NamedDomainObjectContainer by container, PolymorphicDomainObjectContainer { + override val delegate: NamedDomainObjectContainer +) : NamedDomainObjectContainerDelegate(), PolymorphicDomainObjectContainer { companion object { fun of(container: NamedDomainObjectContainer) = @@ -260,7 +262,7 @@ private constructor( * @see [NamedDomainObjectContainer.named] */ operator fun String.invoke(): NamedDomainObjectProvider = - container.named(this) + delegate.named(this) /** * Configures an object by name, without triggering its creation or configuration, failing if there is no such object. @@ -269,7 +271,7 @@ private constructor( * @see [NamedDomainObjectProvider.configure] */ operator fun String.invoke(type: KClass, configuration: U.() -> Unit): NamedDomainObjectProvider = - container.named(this, type, configuration) + delegate.named(this, type, configuration) /** * Locates an object by name and type, without triggering its creation or configuration, failing if there is no such object. @@ -277,7 +279,7 @@ private constructor( * @see [PolymorphicDomainObjectContainer.named] */ operator fun String.invoke(type: KClass): NamedDomainObjectProvider = - container.named(this, type) + delegate.named(this, type) /** * Cast this to [PolymorphicDomainObjectContainer] or throw [IllegalArgumentException]. @@ -289,8 +291,8 @@ private constructor( */ private fun polymorphicDomainObjectContainer() = - container as? PolymorphicDomainObjectContainer - ?: throw IllegalArgumentException("Container '$container' is not polymorphic.") + delegate as? PolymorphicDomainObjectContainer + ?: throw IllegalArgumentException("Container '$delegate' is not polymorphic.") } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/PluginDependenciesSpecScope.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/PluginDependenciesSpecScope.kt index c2ec6a85e4c56..f7bbfb8b6e631 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/PluginDependenciesSpecScope.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/PluginDependenciesSpecScope.kt @@ -29,7 +29,13 @@ import org.gradle.plugin.use.PluginDependencySpec * @see [PluginDependenciesSpec] */ @GradleDsl -class PluginDependenciesSpecScope internal constructor(plugins: PluginDependenciesSpec) : PluginDependenciesSpec by plugins +class PluginDependenciesSpecScope internal constructor( + private val plugins: PluginDependenciesSpec +) : PluginDependenciesSpec { + + override fun id(id: String): PluginDependencySpec = + plugins.id(id) +} /** diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ScriptHandlerScope.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ScriptHandlerScope.kt index a7e95e5ff6c31..499b86927b4bd 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ScriptHandlerScope.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/ScriptHandlerScope.kt @@ -31,6 +31,7 @@ import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.initialization.dsl.ScriptHandler import org.gradle.api.initialization.dsl.ScriptHandler.CLASSPATH_CONFIGURATION +import org.gradle.kotlin.dsl.support.delegates.ScriptHandlerDelegate import org.gradle.kotlin.dsl.support.unsafeLazy @@ -39,8 +40,8 @@ import org.gradle.kotlin.dsl.support.unsafeLazy */ class ScriptHandlerScope private constructor( - scriptHandler: ScriptHandler -) : ScriptHandler by scriptHandler { + override val delegate: ScriptHandler +) : ScriptHandlerDelegate() { companion object { fun of(scriptHandler: ScriptHandler) = @@ -50,7 +51,7 @@ private constructor( /** * The dependencies of the script. */ - val dependencies by unsafeLazy { DependencyHandlerScope.of(scriptHandler.dependencies) } + val dependencies by unsafeLazy { DependencyHandlerScope.of(delegate.dependencies) } /** * The script classpath configuration. @@ -156,6 +157,7 @@ private constructor( * @return the added dependency constraint * * @see [DependencyConstraintHandler.add] + * @since 5.0 */ @Incubating fun DependencyConstraintHandler.classpath(dependencyConstraintNotation: Any): DependencyConstraint? = @@ -170,6 +172,7 @@ private constructor( * @return the added dependency constraint * * @see [DependencyConstraintHandler.add] + * @since 5.0 */ @Incubating fun DependencyConstraintHandler.classpath(dependencyConstraintNotation: Any, configuration: DependencyConstraint.() -> Unit): DependencyConstraint? = diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/TaskContainerExtensions.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/TaskContainerExtensions.kt index 74f5f6b7cce19..26a959bebe0c1 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/TaskContainerExtensions.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/TaskContainerExtensions.kt @@ -20,6 +20,8 @@ import org.gradle.api.tasks.TaskCollection import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider +import org.gradle.kotlin.dsl.support.delegates.TaskContainerDelegate + import kotlin.reflect.KClass import kotlin.reflect.KProperty @@ -145,13 +147,16 @@ operator fun RegisteringDomainObjectDelegateProviderWithTypeAndAction class TaskContainerScope private constructor( val container: TaskContainer -) : TaskContainer by container { +) : TaskContainerDelegate() { companion object { fun of(container: TaskContainer) = TaskContainerScope(container) } + override val delegate: TaskContainer + get() = container + /** * Configures a task by name, without triggering its creation or configuration, failing if there is no such task. * diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragment.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragment.kt index 60ac7a03de55b..586d2c113dc9f 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragment.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragment.kt @@ -19,11 +19,10 @@ package org.gradle.kotlin.dsl.accessors import kotlinx.metadata.jvm.JvmMethodSignature import kotlinx.metadata.jvm.KotlinClassMetadata -import org.gradle.kotlin.dsl.support.bytecode.InternalName +import org.gradle.internal.classanalysis.AsmConstants.ASM_LEVEL import org.jetbrains.org.objectweb.asm.ClassVisitor import org.jetbrains.org.objectweb.asm.ClassWriter -import org.jetbrains.org.objectweb.asm.Opcodes internal @@ -43,7 +42,7 @@ internal class BytecodeFragmentScope( val signature: JvmMethodSignature, writer: ClassWriter -) : ClassVisitor(Opcodes.ASM6, writer) +) : ClassVisitor(ASM_LEVEL, writer) internal @@ -58,4 +57,4 @@ data class MetadataFragmentScope( internal -typealias Fragments = Pair> +typealias Fragments = Pair> diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragments.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragments.kt index a7df44d5d4416..1eb879094dfff 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragments.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorFragments.kt @@ -66,7 +66,7 @@ private fun fragmentsForConfiguration(accessor: Accessor.ForConfiguration): Fragments = accessor.run { val propertyName = name.original - val className = InternalName("$packagePath/${propertyName.capitalize()}ConfigurationAccessorsKt") + val className = "${propertyName.capitalize()}ConfigurationAccessorsKt" className to sequenceOf( AccessorFragment( @@ -800,12 +800,8 @@ val kotlinPrimitiveTypes = primitiveTypeStrings.asSequence().map { (jvmName, kot private -fun internalNameForAccessorClassOf(accessorSpec: TypedAccessorSpec): InternalName = - InternalName("$packagePath/Accessors${hashOf(accessorSpec)}Kt") - - -internal -const val packagePath = "org/gradle/kotlin/dsl" +fun internalNameForAccessorClassOf(accessorSpec: TypedAccessorSpec): String = + "Accessors${hashOf(accessorSpec)}Kt" private diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorsClassPath.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorsClassPath.kt index 358df55cb3517..2f399175cca5d 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorsClassPath.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/AccessorsClassPath.kt @@ -21,6 +21,8 @@ import org.gradle.api.internal.project.ProjectInternal import org.gradle.cache.internal.CacheKeyBuilder.CacheKeySpec +import org.gradle.internal.classanalysis.AsmConstants.ASM_LEVEL + import org.gradle.internal.classpath.ClassPath import org.gradle.internal.classpath.DefaultClassPath @@ -29,7 +31,8 @@ import org.gradle.internal.hash.Hasher import org.gradle.internal.hash.Hashing import org.gradle.kotlin.dsl.cache.ScriptCache -import org.gradle.kotlin.dsl.codegen.fileHeader +import org.gradle.kotlin.dsl.codegen.fileHeaderFor +import org.gradle.kotlin.dsl.codegen.kotlinDslPackageName import org.gradle.kotlin.dsl.concurrent.IO import org.gradle.kotlin.dsl.concurrent.withAsynchronousIO @@ -49,7 +52,6 @@ import org.jetbrains.org.objectweb.asm.ClassReader import org.jetbrains.org.objectweb.asm.ClassVisitor import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PUBLIC import org.jetbrains.org.objectweb.asm.Opcodes.ACC_SYNTHETIC -import org.jetbrains.org.objectweb.asm.Opcodes.ASM6 import org.jetbrains.org.objectweb.asm.signature.SignatureReader import org.jetbrains.org.objectweb.asm.signature.SignatureVisitor @@ -93,7 +95,12 @@ data class AccessorsClassPath(val bin: ClassPath, val src: ClassPath) { internal -fun cachedAccessorsClassPathFor(project: Project, cacheKeySpec: CacheKeySpec, builder: (File, File) -> Unit): AccessorsClassPath { +fun cachedAccessorsClassPathFor( + project: Project, + cacheKeySpec: CacheKeySpec, + builder: (File, File) -> Unit +): AccessorsClassPath { + val cacheDir = scriptCacheOf(project) .cacheDirFor(cacheKeySpec) { baseDir -> @@ -102,6 +109,7 @@ fun cachedAccessorsClassPathFor(project: Project, cacheKeySpec: CacheKeySpec, bu accessorsClassesDir(baseDir) ) } + return AccessorsClassPath( DefaultClassPath.of(accessorsClassesDir(cacheDir)), DefaultClassPath.of(accessorsSourceDir(cacheDir)) @@ -127,7 +135,6 @@ fun configuredProjectSchemaOf(project: Project): TypedProjectSchema? = } else null -internal fun schemaFor(project: Project): TypedProjectSchema = projectSchemaProviderOf(project).schemaFor(project) @@ -141,22 +148,48 @@ private fun scriptCacheOf(project: Project) = project.serviceOf() -internal fun IO.buildAccessorsFor( projectSchema: TypedProjectSchema, classPath: ClassPath, srcDir: File, - binDir: File + binDir: File?, + packageName: String = kotlinDslPackageName, + format: AccessorFormat = AccessorFormats.default ) { val availableSchema = availableProjectSchemaFor(projectSchema, classPath) emitAccessorsFor( availableSchema, srcDir, - binDir + binDir, + OutputPackage(packageName), + format ) } +typealias AccessorFormat = (String) -> String + + +object AccessorFormats { + + val default: AccessorFormat = { accessor -> + accessor.replaceIndent() + } + + val `internal`: AccessorFormat = { accessor -> + accessor + .replaceIndent() + .let { valFunOrClass.matcher(it) } + .replaceAll("internal\n$1 ") + } + + private + val valFunOrClass by lazy { + "^(val|fun|class) ".toRegex(RegexOption.MULTILINE).toPattern() + } +} + + internal fun importsRequiredBy(candidateTypes: List): List = defaultPackageTypesIn( @@ -341,13 +374,13 @@ fun classNamesFromTypeString(typeString: String): ClassNamesFromTypeString { private -class HasTypeParameterClassVisitor : ClassVisitor(ASM6) { +class HasTypeParameterClassVisitor : ClassVisitor(ASM_LEVEL) { var hasTypeParameters = false override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array?) { if (signature != null) { - SignatureReader(signature).accept(object : SignatureVisitor(ASM6) { + SignatureReader(signature).accept(object : SignatureVisitor(ASM_LEVEL) { override fun visitFormalTypeParameter(name: String) { hasTypeParameters = true } @@ -358,7 +391,7 @@ class HasTypeParameterClassVisitor : ClassVisitor(ASM6) { private -class KotlinVisibilityClassVisitor : ClassVisitor(ASM6) { +class KotlinVisibilityClassVisitor : ClassVisitor(ASM_LEVEL) { var visibility: Visibility? = null @@ -378,7 +411,7 @@ class KotlinVisibilityClassVisitor : ClassVisitor(ASM6) { private class ClassDataFromKotlinMetadataAnnotationVisitor( private val onClassData: (ProtoBuf.Class) -> Unit -) : AnnotationVisitor(ASM6) { +) : AnnotationVisitor(ASM_LEVEL) { /** * @see kotlin.Metadata.data1 @@ -408,7 +441,7 @@ class ClassDataFromKotlinMetadataAnnotationVisitor( private -class AnnotationValueCollector(val output: MutableList) : AnnotationVisitor(ASM6) { +class AnnotationValueCollector(val output: MutableList) : AnnotationVisitor(ASM_LEVEL) { override fun visit(name: String?, value: Any?) { @Suppress("unchecked_cast") output.add(value as T) @@ -472,7 +505,6 @@ fun cacheKeyFor(projectSchema: TypedProjectSchema, classPath: ClassPath): CacheK + classPath) -internal fun hashCodeFor(schema: TypedProjectSchema): HashCode = Hashing.newHasher().run { putAll(schema.extensions) putAll(schema.conventions) @@ -509,9 +541,14 @@ fun enabledJitAccessors(project: Project) = internal -fun IO.writeAccessorsTo(outputFile: File, accessors: List, imports: List = emptyList()) = io { +fun IO.writeAccessorsTo( + outputFile: File, + accessors: Iterable, + imports: List = emptyList(), + packageName: String = kotlinDslPackageName +) = io { outputFile.bufferedWriter().useToRun { - appendReproducibleNewLine(fileHeaderWithImports) + appendReproducibleNewLine(fileHeaderWithImportsFor(packageName)) if (imports.isNotEmpty()) { imports.forEach { appendReproducibleNewLine("import $it") @@ -519,7 +556,7 @@ fun IO.writeAccessorsTo(outputFile: File, accessors: List, imports: List appendReproducibleNewLine() } accessors.forEach { - appendReproducibleNewLine(it.replaceIndent()) + appendReproducibleNewLine(it) appendReproducibleNewLine() } } @@ -527,8 +564,8 @@ fun IO.writeAccessorsTo(outputFile: File, accessors: List, imports: List internal -val fileHeaderWithImports = """ -$fileHeader +fun fileHeaderWithImportsFor(accessorsPackage: String = kotlinDslPackageName) = """ +${fileHeaderFor(accessorsPackage)} import org.gradle.api.Action import org.gradle.api.Incubating diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/CodeGenerator.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/CodeGenerator.kt index f1b1e18595639..c34fcb5a7db38 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/CodeGenerator.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/CodeGenerator.kt @@ -76,7 +76,7 @@ private fun inaccessibleExtensionAccessorFor(targetType: String, name: AccessorNameSpec, typeAccess: TypeAccessibility.Inaccessible): String = name.run { """ /** - * Retrieves the [$original][${typeAccess.type}] extension. + * Retrieves the `$original` extension. * * ${documentInaccessibilityReasons(name, typeAccess)} */ @@ -84,7 +84,7 @@ fun inaccessibleExtensionAccessorFor(targetType: String, name: AccessorNameSpec, $thisExtensions.getByName("$stringLiteral") /** - * Configures the [$original][${typeAccess.type}] extension. + * Configures the `$original` extension. * * ${documentInaccessibilityReasons(name, typeAccess)} */ @@ -127,7 +127,7 @@ private fun inaccessibleConventionAccessorFor(targetType: String, name: AccessorNameSpec, typeAccess: TypeAccessibility.Inaccessible): String = name.run { """ /** - * Retrieves the [$original][${typeAccess.type}] convention. + * Retrieves the `$original` convention. * * ${documentInaccessibilityReasons(name, typeAccess)} */ @@ -135,7 +135,7 @@ fun inaccessibleConventionAccessorFor(targetType: String, name: AccessorNameSpec $thisConvention.getPluginByName("$stringLiteral") /** - * Configures the [$original][${typeAccess.type}] convention. + * Configures the `$original` convention. * * ${documentInaccessibilityReasons(name, typeAccess)} */ @@ -172,7 +172,7 @@ private fun inaccessibleExistingTaskAccessorFor(name: AccessorNameSpec, typeAccess: TypeAccessibility.Inaccessible): String = name.run { """ /** - * Provides the existing [$original][${typeAccess.type}] task. + * Provides the existing `$original` task. * * ${documentInaccessibilityReasons(name, typeAccess)} */ @@ -209,7 +209,7 @@ private fun inaccessibleExistingContainerElementAccessorFor(containerType: String, name: AccessorNameSpec, elementType: TypeAccessibility.Inaccessible): String = name.run { """ /** - * Provides the existing [$original][${elementType.type}] element. + * Provides the existing `$original` element. * * ${documentInaccessibilityReasons(name, elementType)} */ diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/Emitter.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/Emitter.kt index 92caf9d53a201..afbbe3b7512f5 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/Emitter.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/Emitter.kt @@ -37,56 +37,121 @@ internal fun IO.emitAccessorsFor( projectSchema: ProjectSchema, srcDir: File, - binDir: File + binDir: File?, + outputPackage: OutputPackage, + format: AccessorFormat ): List { - makeAccessorOutputDirs(srcDir, binDir) + makeAccessorOutputDirs(srcDir, binDir, outputPackage.path) val emittedClassNames = accessorsFor(projectSchema).map { accessor -> - emitClassFor(accessor, srcDir, binDir) + emitClassFor( + accessor, + srcDir, + binDir, + outputPackage, + format + ) }.toList() - writeFile( - moduleFileFor(binDir), - moduleMetadataBytesFor(emittedClassNames) - ) + if (binDir != null) { + writeFile( + moduleFileFor(binDir), + moduleMetadataBytesFor(emittedClassNames) + ) + } return emittedClassNames } internal -fun IO.makeAccessorOutputDirs(srcDir: File, binDir: File) = io { +fun IO.makeAccessorOutputDirs(srcDir: File, binDir: File?, packagePath: String) = io { srcDir.resolve(packagePath).mkdirs() - binDir.resolve(packagePath).mkdirs() - binDir.resolve("META-INF").mkdir() + binDir?.apply { + resolve(packagePath).mkdirs() + resolve("META-INF").mkdir() + } +} + + +internal +data class OutputPackage(val name: String) { + + val path by lazy { + name.replace('.', '/') + } } private -fun IO.emitClassFor(accessor: Accessor, srcDir: File, binDir: File): InternalName { +fun IO.emitClassFor( + accessor: Accessor, + srcDir: File, + binDir: File?, + outputPackage: OutputPackage, + format: AccessorFormat +): InternalName { - val (className, fragments) = fragmentsFor(accessor) + val (simpleClassName, fragments) = fragmentsFor(accessor) + val className = InternalName("${outputPackage.path}/$simpleClassName") val sourceCode = mutableListOf() + + fun collectSourceFragment(source: String) { + sourceCode.add(format(source)) + } + + if (binDir != null) { + writeAccessorsBytecodeTo( + binDir, + className, + fragments, + ::collectSourceFragment + ) + } else { + for ((source, _, _, _) in fragments) { + collectSourceFragment(source) + } + } + + writeAccessorsTo( + sourceFileFor(className, srcDir), + sourceCode, + importsRequiredBy(accessor), + outputPackage.name + ) + + return className +} + + +private +fun sourceFileFor(className: InternalName, srcDir: File) = + srcDir.resolve("${className.value.removeSuffix("Kt")}.kt") + + +private +fun IO.writeAccessorsBytecodeTo( + binDir: File, + className: InternalName, + fragments: Sequence, + collectSourceFragment: (String) -> Unit +) { + val metadataWriter = beginFileFacadeClassHeader() val classWriter = beginPublicClass(className) for ((source, bytecode, metadata, signature) in fragments) { - sourceCode.add(source) + collectSourceFragment(source) MetadataFragmentScope(signature, metadataWriter).run(metadata) BytecodeFragmentScope(signature, classWriter).run(bytecode) } - val sourceFile = srcDir.resolve("${className.value.removeSuffix("Kt")}.kt") - writeAccessorsTo(sourceFile, sourceCode, importsRequiredBy(accessor)) - val classHeader = metadataWriter.closeHeader() val classBytes = classWriter.endKotlinClass(classHeader) val classFile = binDir.resolve("$className.class") writeFile(classFile, classBytes) - - return className } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/PluginAccessorsClassPath.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/PluginAccessorsClassPath.kt index d3013656c8a37..8f5c3baf0cfb7 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/PluginAccessorsClassPath.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/accessors/PluginAccessorsClassPath.kt @@ -27,9 +27,12 @@ import org.gradle.api.internal.project.ProjectInternal import org.gradle.internal.classpath.ClassPath import org.gradle.kotlin.dsl.codegen.fileHeader +import org.gradle.kotlin.dsl.codegen.fileHeaderFor +import org.gradle.kotlin.dsl.codegen.kotlinDslPackagePath import org.gradle.kotlin.dsl.codegen.pluginEntriesFrom import org.gradle.kotlin.dsl.concurrent.IO import org.gradle.kotlin.dsl.concurrent.withAsynchronousIO +import org.gradle.kotlin.dsl.concurrent.withSynchronousIO import org.gradle.kotlin.dsl.concurrent.writeFile import org.gradle.kotlin.dsl.provider.kotlinScriptClassPathProviderOf @@ -66,6 +69,7 @@ import org.gradle.plugin.use.PluginDependencySpec import org.jetbrains.org.objectweb.asm.ClassWriter import org.jetbrains.org.objectweb.asm.MethodVisitor +import java.io.BufferedWriter import java.io.File @@ -75,7 +79,7 @@ import java.io.File * * The accessors provide content-assist for plugin ids and quick navigation to the plugin source code. */ -fun pluginAccessorsClassPath(project: Project): AccessorsClassPath = project.rootProject.let { rootProject -> +fun pluginSpecBuildersClassPath(project: Project): AccessorsClassPath = project.rootProject.let { rootProject -> rootProject.getOrCreateProperty("gradleKotlinDsl.pluginAccessorsClassPath") { val buildSrcClassLoaderScope = baseClassLoaderScopeOf(rootProject) @@ -95,24 +99,45 @@ fun pluginAccessorsClassPath(project: Project): AccessorsClassPath = project.roo } +fun writeSourceCodeForPluginSpecBuildersFor( + pluginDescriptorsClassPath: ClassPath, + sourceFile: File, + packageName: String +) { + withSynchronousIO { + writePluginAccessorsSourceCodeTo( + sourceFile, + pluginAccessorsFor(pluginDescriptorsClassPath), + format = AccessorFormats.internal, + header = fileHeaderFor(packageName) + ) + } +} + + +private +fun pluginAccessorsFor(pluginDescriptorsClassPath: ClassPath): List = + pluginAccessorsFor(pluginTreesFrom(pluginDescriptorsClassPath)).toList() + + internal fun IO.buildPluginAccessorsFor( pluginDescriptorsClassPath: ClassPath, srcDir: File, binDir: File ) { - makeAccessorOutputDirs(srcDir, binDir) + makeAccessorOutputDirs(srcDir, binDir, kotlinDslPackagePath) - val pluginSpecs = pluginSpecsFrom(pluginDescriptorsClassPath) - val pluginTrees = PluginTree.of(pluginSpecs) - val accessorList = pluginAccessorsFor(pluginTrees).toList() - val baseFileName = "$packagePath/PluginAccessors" + val pluginTrees = pluginTreesFrom(pluginDescriptorsClassPath) + + val baseFileName = "$kotlinDslPackagePath/PluginAccessors" val sourceFile = srcDir.resolve("$baseFileName.kt") + val accessorList = pluginAccessorsFor(pluginTrees).toList() writePluginAccessorsSourceCodeTo(sourceFile, accessorList) val fileFacadeClassName = InternalName(baseFileName + "Kt") - val moduleName = "kotlin-dsl-plugin-spec-accessors" + val moduleName = "kotlin-dsl-plugin-spec-builders" val moduleMetadata = moduleMetadataBytesFor(listOf(fileFacadeClassName)) writeFile( moduleFileFor(binDir, moduleName), @@ -154,6 +179,11 @@ fun IO.buildPluginAccessorsFor( } +internal +fun pluginTreesFrom(pluginDescriptorsClassPath: ClassPath): Map = + PluginTree.of(pluginSpecsFrom(pluginDescriptorsClassPath)) + + private fun ClassWriter.emitAccessorMethodFor(accessor: PluginAccessor, signature: JvmMethodSignature) { val extension = accessor.extension @@ -180,50 +210,64 @@ fun ClassWriter.emitAccessorMethodFor(accessor: PluginAccessor, signature: JvmMe private -fun IO.writePluginAccessorsSourceCodeTo(sourceFile: File, accessors: List) = io { +fun IO.writePluginAccessorsSourceCodeTo( + sourceFile: File, + accessors: List, + format: AccessorFormat = AccessorFormats.default, + header: String = fileHeader +) = io { sourceFile.bufferedWriter().useToRun { - appendReproducibleNewLine(fileHeader) + appendReproducibleNewLine(header) + appendSourceCodeForPluginAccessors(accessors, format) + } +} - appendReproducibleNewLine(""" - import ${PluginDependenciesSpec::class.qualifiedName} - import ${PluginDependencySpec::class.qualifiedName} - """.replaceIndent()) - defaultPackageTypesIn(accessors).forEach { - appendReproducibleNewLine("import $it") - } +private +fun BufferedWriter.appendSourceCodeForPluginAccessors( + accessors: List, + format: AccessorFormat +) { - accessors.runEach { - newLine() - newLine() - val extendedType = extension.receiverType.sourceName - val pluginsRef = pluginDependenciesSpecOf(extendedType) - when (this) { - is PluginAccessor.ForPlugin -> { - appendReproducibleNewLine(""" - /** - * The `$id` plugin implemented by [$implementationClass]. - */ - val `$extendedType`.`${extension.name}`: PluginDependencySpec - get() = $pluginsRef.id("$id") - """.replaceIndent()) - } - is PluginAccessor.ForGroup -> { - val groupType = extension.returnType.sourceName - appendReproducibleNewLine(""" - /** - * The `$id` plugin group. - */ - class `$groupType`(internal val plugins: PluginDependenciesSpec) - - - /** - * Plugin ids starting with `$id`. - */ - val `$extendedType`.`${extension.name}`: `$groupType` - get() = `$groupType`($pluginsRef) - """.replaceIndent()) - } + appendReproducibleNewLine(""" + import ${PluginDependenciesSpec::class.qualifiedName} + import ${PluginDependencySpec::class.qualifiedName} + """.replaceIndent()) + + defaultPackageTypesIn(accessors).forEach { + appendReproducibleNewLine("import $it") + } + + accessors.runEach { + newLine() + newLine() + val extendedType = extension.receiverType.sourceName + val pluginsRef = pluginDependenciesSpecOf(extendedType) + when (this) { + is PluginAccessor.ForPlugin -> { + appendReproducibleNewLine(format(""" + /** + * The `$id` plugin implemented by [$implementationClass]. + */ + val `$extendedType`.`${extension.name}`: PluginDependencySpec + get() = $pluginsRef.id("$id") + """)) + } + is PluginAccessor.ForGroup -> { + val groupType = extension.returnType.sourceName + appendReproducibleNewLine(format(""" + /** + * The `$id` plugin group. + */ + class `$groupType`(internal val plugins: PluginDependenciesSpec) + + + /** + * Plugin ids starting with `$id`. + */ + val `$extendedType`.`${extension.name}`: `$groupType` + get() = `$groupType`($pluginsRef) + """)) } } } @@ -336,7 +380,7 @@ fun pluginAccessorsFor(pluginTrees: Map, extendedType: TypeS internal fun typeSpecForPluginGroupType(groupType: String) = - TypeSpec(groupType, InternalName("$packagePath/$groupType")) + TypeSpec(groupType, InternalName("$kotlinDslPackagePath/$groupType")) internal diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/cache/BuildCacheCommands.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/cache/BuildCacheCommands.kt index 60d3031ddf1b4..8d6b5a419e5cd 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/cache/BuildCacheCommands.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/cache/BuildCacheCommands.kt @@ -41,6 +41,8 @@ class ScriptBuildCacheKey( override fun getHashCode(): String = cacheKey + override fun toByteArray(): ByteArray = throw UnsupportedOperationException() + override fun toString(): String = cacheKey } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/cache/ScriptCache.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/cache/ScriptCache.kt index 29c3a83367010..15d154695c391 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/cache/ScriptCache.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/cache/ScriptCache.kt @@ -96,7 +96,7 @@ class ScriptCache( val buildCacheKey = ScriptBuildCacheKey(displayName, cacheKey) val buildInvocationId = buildInvocationIdOf(scriptTarget) val existing = cacheController.load(LoadDirectory(cacheDir, buildCacheKey, buildInvocationId)) - if (existing === null) { + if (!existing.isPresent) { val executionTime = executionTimeMillisOf { initializer(cacheDir) diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/ApiExtensionsJar.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/ApiExtensionsJar.kt index bb7ac840504be..1eecbc0e071ba 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/ApiExtensionsJar.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/ApiExtensionsJar.kt @@ -98,7 +98,7 @@ class ApiExtensionsJarGenerator( "$packageDir/$fileName" private - val packageDir = packageName.replace('.', '/') + val packageDir = kotlinDslPackagePath } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/ApiTypeProvider.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/ApiTypeProvider.kt index c8b1be3ed45aa..3800a78864029 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/ApiTypeProvider.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/ApiTypeProvider.kt @@ -16,10 +16,15 @@ package org.gradle.kotlin.dsl.codegen +import com.google.common.annotations.VisibleForTesting + import org.gradle.api.Incubating +import org.gradle.internal.classanalysis.AsmConstants.ASM_LEVEL + import org.gradle.kotlin.dsl.accessors.contains import org.gradle.kotlin.dsl.accessors.primitiveTypeStrings + import org.gradle.kotlin.dsl.support.ClassBytesRepository import org.gradle.kotlin.dsl.support.classPathBytesRepositoryFor import org.gradle.kotlin.dsl.support.unsafeLazy @@ -36,7 +41,6 @@ import org.jetbrains.org.objectweb.asm.Opcodes.ACC_PUBLIC import org.jetbrains.org.objectweb.asm.Opcodes.ACC_STATIC import org.jetbrains.org.objectweb.asm.Opcodes.ACC_SYNTHETIC import org.jetbrains.org.objectweb.asm.Opcodes.ACC_VARARGS -import org.jetbrains.org.objectweb.asm.Opcodes.ASM6 import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.TypePath import org.jetbrains.org.objectweb.asm.signature.SignatureReader @@ -45,10 +49,9 @@ import org.jetbrains.org.objectweb.asm.tree.AnnotationNode import org.jetbrains.org.objectweb.asm.tree.ClassNode import org.jetbrains.org.objectweb.asm.tree.MethodNode -import com.google.common.annotations.VisibleForTesting - import java.io.Closeable import java.io.File + import java.util.ArrayDeque import javax.annotation.Nullable @@ -430,7 +433,7 @@ inline fun List?.has() = private -class ApiTypeClassNode : ClassNode(ASM6) { +class ApiTypeClassNode : ClassNode(ASM_LEVEL) { override fun visitSource(file: String?, debug: String?) = Unit override fun visitOuterClass(owner: String?, name: String?, desc: String?) = Unit @@ -442,7 +445,7 @@ class ApiTypeClassNode : ClassNode(ASM6) { private -abstract class BaseSignatureVisitor : SignatureVisitor(ASM6) { +abstract class BaseSignatureVisitor : SignatureVisitor(ASM_LEVEL) { val typeParameters: MutableMap> = LinkedHashMap(1) @@ -486,7 +489,7 @@ class MethodSignatureVisitor : BaseSignatureVisitor() { private -class TypeSignatureVisitor(val variance: Variance = Variance.INVARIANT) : SignatureVisitor(ASM6) { +class TypeSignatureVisitor(val variance: Variance = Variance.INVARIANT) : SignatureVisitor(ASM_LEVEL) { var isArray = false diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/PluginIdExtensions.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/PluginIdExtensions.kt index 4d6dafc6e76a6..2b5259406701c 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/PluginIdExtensions.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/PluginIdExtensions.kt @@ -22,6 +22,7 @@ import org.gradle.plugin.use.PluginDependencySpec import java.io.File import java.util.Properties +import java.util.jar.JarEntry import java.util.jar.JarFile @@ -248,7 +249,7 @@ internal fun pluginEntriesFrom(jar: File): List = JarFile(jar).use { jarFile -> jarFile.entries().asSequence().filter { - it.isFile && it.name.startsWith("META-INF/gradle-plugins/") + isGradlePluginPropertiesFile(it) }.map { pluginEntry -> val pluginProperties = jarFile.getInputStream(pluginEntry).use { Properties().apply { load(it) } } val id = pluginEntry.name.substringAfterLast("/").substringBeforeLast(".properties") @@ -256,3 +257,9 @@ fun pluginEntriesFrom(jar: File): List = PluginEntry(id, implementationClass) }.toList() } + + +private +fun isGradlePluginPropertiesFile(entry: JarEntry) = entry.run { + isFile && name.run { startsWith("META-INF/gradle-plugins/") && endsWith(".properties") } +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/SourceFileHeader.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/SourceFileHeader.kt index bb37f7392185a..9cb30f1c51192 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/SourceFileHeader.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/codegen/SourceFileHeader.kt @@ -19,7 +19,7 @@ package org.gradle.kotlin.dsl.codegen internal val fileHeader: String - get() = fileHeaderFor(packageName) + get() = fileHeaderFor(kotlinDslPackageName) internal @@ -27,16 +27,28 @@ fun fileHeaderFor(packageName: String) = """$licenseHeader @file:Suppress( + "unused", + "nothing_to_inline", + "useless_cast", + "unchecked_cast", + "extension_shadowed_by_member", "redundant_projection", - "nothing_to_inline" + "RemoveRedundantBackticks", + "ObjectPropertyName" ) +/* ktlint-disable */ + package $packageName """ internal -const val packageName = "org.gradle.kotlin.dsl" +const val kotlinDslPackageName = "org.gradle.kotlin.dsl" + + +internal +const val kotlinDslPackagePath = "org/gradle/kotlin/dsl" internal diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/concurrent/IO.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/concurrent/IO.kt index 43e1ed48fe30a..e1699f0937f9a 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/concurrent/IO.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/concurrent/IO.kt @@ -69,8 +69,19 @@ interface AsyncIOScopeFactory { } -internal -inline fun withAsynchronousIO( +fun withAsynchronousIO( project: Project, action: IO.() -> T ): T = project.serviceOf().newScope().useToRun(action) + + +internal +inline fun withSynchronousIO(action: IO.() -> Unit) { + action(SynchronousIO) +} + + +internal +object SynchronousIO : IO { + override fun io(action: () -> Unit) = action() +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/Lexer.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/Lexer.kt index 518e8f4b7388e..b71aba7925789 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/Lexer.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/Lexer.kt @@ -152,7 +152,10 @@ fun topLevelBlock(identifier: String, identifierRange: IntRange, blockRange: Int internal -data class TopLevelBlock(val identifier: String, val section: ScriptSection) +data class TopLevelBlock(val identifier: String, val section: ScriptSection) { + val range: IntRange + get() = section.wholeRange +} internal @@ -162,6 +165,6 @@ fun List.singleBlockSectionOrNull(): ScriptSection? = 1 -> get(0).section else -> { val unexpectedBlock = get(1) - throw UnexpectedBlock(unexpectedBlock.identifier, unexpectedBlock.section.wholeRange) + throw UnexpectedBlock(unexpectedBlock.identifier, unexpectedBlock.range) } } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ProgramParser.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ProgramParser.kt index e2788dc61266b..dc93033731898 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ProgramParser.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ProgramParser.kt @@ -75,8 +75,8 @@ object ProgramParser { sourceWithoutComments.map { it.erase( listOfNotNull( - buildscriptFragment?.section?.wholeRange, - pluginsFragment?.section?.wholeRange)) + buildscriptFragment?.range, + pluginsFragment?.range)) } val stage2 = remainingSource diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ProgramSource.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ProgramSource.kt index e4e232e55447b..f3703c0975789 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ProgramSource.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ProgramSource.kt @@ -94,8 +94,11 @@ data class ProgramSourceFragment( val lineNumber: Int get() = source.contents.lineNumberOf(section.identifier.start) + val range: IntRange + get() = section.wholeRange + override fun toString(): String = - "ProgramSourceFragment(\"${source.text.subSequence(section.wholeRange)}\")" + "ProgramSourceFragment(\"${source.text.subSequence(range)}\")" } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ResidualProgramCompiler.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ResidualProgramCompiler.kt index 4c47614fa209d..bc0c015ecb65b 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ResidualProgramCompiler.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/execution/ResidualProgramCompiler.kt @@ -36,6 +36,7 @@ import org.gradle.kotlin.dsl.support.KotlinInitscriptBlock import org.gradle.kotlin.dsl.support.KotlinPluginsBlock import org.gradle.kotlin.dsl.support.KotlinScriptHost import org.gradle.kotlin.dsl.support.KotlinSettingsBuildscriptBlock + import org.gradle.kotlin.dsl.support.bytecode.ACONST_NULL import org.gradle.kotlin.dsl.support.bytecode.ALOAD import org.gradle.kotlin.dsl.support.bytecode.ARETURN @@ -57,10 +58,12 @@ import org.gradle.kotlin.dsl.support.bytecode.loadByteArray import org.gradle.kotlin.dsl.support.bytecode.publicClass import org.gradle.kotlin.dsl.support.bytecode.publicDefaultConstructor import org.gradle.kotlin.dsl.support.bytecode.publicMethod + import org.gradle.kotlin.dsl.support.compileKotlinScriptToDirectory import org.gradle.kotlin.dsl.support.messageCollectorFor import org.gradle.plugin.management.internal.DefaultPluginRequests + import org.gradle.plugin.use.internal.PluginRequestCollector import org.jetbrains.kotlin.script.KotlinScriptDefinition @@ -227,8 +230,8 @@ class ResidualProgramCompiler( compileStage1( plugins.fragment.source.map { it.preserve( - buildscript.fragment.section.wholeRange, - plugins.fragment.section.wholeRange) + buildscript.fragment.range, + plugins.fragment.range) }, buildscriptWithPluginsScriptDefinition, pluginsBlockClassPath @@ -467,7 +470,7 @@ class ResidualProgramCompiler( private fun compilePlugins(program: Program.Plugins) = compileStage1( - program.fragment.source.map { it.preserve(program.fragment.section.wholeRange) }, + program.fragment.source.map { it.preserve(program.fragment.range) }, pluginsScriptDefinition, pluginsBlockClassPath ) @@ -659,21 +662,27 @@ class ResidualProgramCompiler( val buildscriptWithPluginsScriptDefinition get() = scriptDefinitionFromTemplate(KotlinBuildscriptAndPluginsBlock::class) + private fun scriptDefinitionFromTemplate(template: KClass) = + scriptDefinitionFromTemplate(template, implicitImports) +} - object : KotlinScriptDefinition(template), DependenciesResolver { - override val dependencyResolver = this +fun scriptDefinitionFromTemplate( + template: KClass, + implicitImports: List +): KotlinScriptDefinition = object : KotlinScriptDefinition(template), DependenciesResolver { - override fun resolve( - scriptContents: ScriptContents, - environment: Environment - ): DependenciesResolver.ResolveResult = DependenciesResolver.ResolveResult.Success( - ScriptDependencies(imports = implicitImports), - emptyList() - ) - } + override val dependencyResolver = this + + override fun resolve( + scriptContents: ScriptContents, + environment: Environment + ): DependenciesResolver.ResolveResult = DependenciesResolver.ResolveResult.Success( + ScriptDependencies(imports = implicitImports), + emptyList() + ) } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/precompile/PrecompiledProjectScript.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/precompile/PrecompiledProjectScript.kt index aed4f50ada4ea..1df0e0d81922d 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/precompile/PrecompiledProjectScript.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/precompile/PrecompiledProjectScript.kt @@ -21,6 +21,7 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.GradleDsl import org.gradle.kotlin.dsl.KotlinScriptTemplate import org.gradle.kotlin.dsl.ScriptHandlerScope +import org.gradle.kotlin.dsl.support.delegates.ProjectDelegate import org.gradle.plugin.use.PluginDependenciesSpec import org.gradle.plugin.use.PluginDependencySpec @@ -55,7 +56,9 @@ import kotlin.script.templates.ScriptTemplateDefinition scriptFilePattern = "^.*\\.gradle\\.kts$") @SamWithReceiverAnnotations("org.gradle.api.HasImplicitReceiver") @GradleDsl -abstract class PrecompiledProjectScript(project: Project) : Project by project { +abstract class PrecompiledProjectScript( + override val delegate: Project +) : ProjectDelegate() { /** * Configures the build script classpath for this project. diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/precompile/PrecompiledScriptDependenciesResolver.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/precompile/PrecompiledScriptDependenciesResolver.kt index 2eb9f1dcd64b7..0403d5bcb4f3a 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/precompile/PrecompiledScriptDependenciesResolver.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/precompile/PrecompiledScriptDependenciesResolver.kt @@ -16,19 +16,26 @@ package org.gradle.kotlin.dsl.precompile +import org.gradle.internal.hash.Hashing + import org.gradle.kotlin.dsl.resolver.KotlinBuildScriptDependencies import java.util.concurrent.Future -import kotlin.script.dependencies.ScriptDependenciesResolver +import kotlin.script.dependencies.Environment +import kotlin.script.dependencies.KotlinScriptExternalDependencies import kotlin.script.dependencies.PseudoFuture import kotlin.script.dependencies.ScriptContents -import kotlin.script.dependencies.KotlinScriptExternalDependencies -import kotlin.script.dependencies.Environment +import kotlin.script.dependencies.ScriptDependenciesResolver class PrecompiledScriptDependenciesResolver : ScriptDependenciesResolver { + companion object { + + fun hashOf(charSequence: CharSequence?) = Hashing.hashString(charSequence).toString() + } + object EnvironmentProperties { const val kotlinDslImplicitImports = "kotlinDslImplicitImports" } @@ -42,12 +49,26 @@ class PrecompiledScriptDependenciesResolver : ScriptDependenciesResolver { PseudoFuture( KotlinBuildScriptDependencies( - imports = implicitImportsFrom(environment), + imports = implicitImportsFrom(environment) + precompiledScriptPluginImportsFrom(environment, script), classpath = emptyList(), - sources = emptyList())) + sources = emptyList() + ) + ) private fun implicitImportsFrom(environment: Environment?) = - (environment?.get(EnvironmentProperties.kotlinDslImplicitImports) as? String)?.split(':') + environment.stringList(EnvironmentProperties.kotlinDslImplicitImports) + + private + fun precompiledScriptPluginImportsFrom(environment: Environment?, script: ScriptContents): List = + environment.stringList(hashOf(script.text)) + + private + fun Environment?.stringList(key: String) = + string(key)?.split(':') ?: emptyList() + + private + fun Environment?.string(key: String) = + this?.get(key) as? String } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/provider/KotlinScriptEvaluator.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/provider/KotlinScriptEvaluator.kt index f966d465610c3..6d6f1a992733e 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/provider/KotlinScriptEvaluator.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/provider/KotlinScriptEvaluator.kt @@ -44,7 +44,7 @@ import org.gradle.internal.operations.CallableBuildOperation import org.gradle.internal.scripts.CompileScriptBuildOperationType.Details import org.gradle.internal.scripts.CompileScriptBuildOperationType.Result -import org.gradle.kotlin.dsl.accessors.pluginAccessorsClassPath +import org.gradle.kotlin.dsl.accessors.pluginSpecBuildersClassPath import org.gradle.kotlin.dsl.cache.ScriptCache @@ -151,7 +151,7 @@ class StandardKotlinScriptEvaluator( override fun pluginAccessorsFor(scriptHost: KotlinScriptHost<*>): ClassPath = (scriptHost.target as? Project)?.let { - pluginAccessorsClassPath(it).bin + pluginSpecBuildersClassPath(it).bin } ?: ClassPath.EMPTY override fun runCompileBuildOperation(scriptPath: String, stage: String, action: () -> String): String = diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/provider/PrecompiledScriptPluginsSupport.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/provider/PrecompiledScriptPluginsSupport.kt new file mode 100644 index 0000000000000..2726095c97085 --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/provider/PrecompiledScriptPluginsSupport.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.provider + +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.SourceDirectorySet +import org.gradle.api.tasks.TaskProvider + +import java.util.function.Consumer + + +interface PrecompiledScriptPluginsSupport { + + @Deprecated("Use enableOn(Target)") + fun enableOn( + project: Project, + kotlinSourceDirectorySet: SourceDirectorySet, + kotlinCompileTask: TaskProvider, + kotlinCompilerArgsConsumer: Consumer> + ) + + fun enableOn(target: Target): Boolean + + interface Target { + val project: Project + val kotlinSourceDirectorySet: SourceDirectorySet + val kotlinCompileTask: TaskProvider + fun applyKotlinCompilerArgs(args: List) + } +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/ExtractGradleSourcesTransform.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/FindGradleSources.kt similarity index 55% rename from subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/ExtractGradleSourcesTransform.kt rename to subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/FindGradleSources.kt index 7aa8e0bd8ee14..6990c6679e1d5 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/ExtractGradleSourcesTransform.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/FindGradleSources.kt @@ -16,10 +16,13 @@ package org.gradle.kotlin.dsl.resolver -import org.gradle.api.artifacts.transform.ArtifactTransform - +import org.gradle.api.artifacts.transform.InputArtifact +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformOutputs +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity import org.gradle.kotlin.dsl.support.unzipTo - import java.io.File @@ -28,20 +31,22 @@ import java.io.File * a downloaded ZIP of the Gradle sources, and will return the list of main sources * subdirectories for all subprojects. */ -class ExtractGradleSourcesTransform : ArtifactTransform() { +abstract class FindGradleSources : TransformAction { + @get:InputArtifact + abstract val input: File - override fun transform(input: File): List { - unzipTo(outputDirectory, input) - return sourceDirectories() + override fun transform(outputs: TransformOutputs) { + registerSourceDirectories(outputs) } private - fun sourceDirectories() = + fun registerSourceDirectories(outputs: TransformOutputs) { unzippedSubProjectsDir()?.let { subDirsOf(it).flatMap { subProject -> subDirsOf(File(subProject, "src/main")) - } - } ?: emptyList() + }.forEach { outputs.dir(it) } + } + } private fun unzippedSubProjectsDir(): File? = @@ -51,5 +56,16 @@ class ExtractGradleSourcesTransform : ArtifactTransform() { private fun unzippedDistroDir(): File? = - outputDirectory.listFiles().singleOrNull() + input.listFiles().singleOrNull() +} + + +abstract class UnzipDistribution : TransformAction { + @get:PathSensitive(PathSensitivity.NONE) + @get:InputArtifact + abstract val input: File + + override fun transform(outputs: TransformOutputs) { + unzipTo(outputs.dir("unzipped-distribution"), input) + } } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/KotlinBuildScriptDependenciesResolver.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/KotlinBuildScriptDependenciesResolver.kt index 7fce79031c470..971cc3f924cf5 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/KotlinBuildScriptDependenciesResolver.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/KotlinBuildScriptDependenciesResolver.kt @@ -139,12 +139,12 @@ class KotlinBuildScriptDependenciesResolver @VisibleForTesting constructor( previousDependencies: KotlinScriptExternalDependencies? ): KotlinScriptExternalDependencies? { - val scriptModelRequest = scriptModelRequestFrom(scriptFile, environment, cid) - log(SubmittedModelRequest(cid, scriptFile, scriptModelRequest)) + val request = scriptModelRequestFrom(scriptFile, environment, cid) + log(SubmittedModelRequest(cid, scriptFile, request)) - val response = DefaultKotlinBuildScriptModelRepository.scriptModelFor(scriptModelRequest) + val response = DefaultKotlinBuildScriptModelRepository.scriptModelFor(request) if (response == null) { - log(RequestCancelled(cid, scriptFile, scriptModelRequest)) + log(RequestCancelled(cid, scriptFile, request)) return null } log(ReceivedModelResponse(cid, scriptFile, response)) @@ -155,7 +155,7 @@ class KotlinBuildScriptDependenciesResolver @VisibleForTesting constructor( return when { response.exceptions.isEmpty() -> - dependenciesFrom(response).also { + dependenciesFrom(request, response).also { log(ResolvedDependencies(cid, scriptFile, it)) } previousDependencies != null && previousDependencies.classpath.count() > response.classPath.size -> @@ -163,7 +163,7 @@ class KotlinBuildScriptDependenciesResolver @VisibleForTesting constructor( log(ResolvedToPreviousWithErrors(cid, scriptFile, previousDependencies, response.exceptions)) } else -> - dependenciesFrom(response).also { + dependenciesFrom(request, response).also { log(ResolvedDependenciesWithErrors(cid, scriptFile, it, response.exceptions)) } } @@ -207,11 +207,12 @@ class KotlinBuildScriptDependenciesResolver @VisibleForTesting constructor( ?: GradleInstallation.Wrapper private - fun dependenciesFrom(response: KotlinBuildScriptModel) = + fun dependenciesFrom(request: KotlinBuildScriptModelRequest, response: KotlinBuildScriptModel) = KotlinBuildScriptDependencies( response.classPath, response.sourcePath, - response.implicitImports + response.implicitImports, + request.javaHome?.path ) } @@ -220,7 +221,8 @@ internal class KotlinBuildScriptDependencies( override val classpath: Iterable, override val sources: Iterable, - override val imports: Iterable + override val imports: Iterable, + override val javaHome: String? = null ) : KotlinScriptExternalDependencies diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/KotlinBuildScriptModelRequest.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/KotlinBuildScriptModelRequest.kt index f1105d62d68a1..20d410ea0b92a 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/KotlinBuildScriptModelRequest.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/KotlinBuildScriptModelRequest.kt @@ -29,6 +29,7 @@ import org.gradle.tooling.ProjectConnection import com.google.common.annotations.VisibleForTesting import java.io.File +import java.util.function.Function @VisibleForTesting @@ -69,45 +70,99 @@ typealias ModelBuilderCustomization = ModelBuilder.() -> fun fetchKotlinBuildScriptModelFor( request: KotlinBuildScriptModelRequest, modelBuilderCustomization: ModelBuilderCustomization = {} -): KotlinBuildScriptModel { +): KotlinBuildScriptModel = + + fetchKotlinBuildScriptModelFor(request.toFetchParametersWith { + setJavaHome(request.javaHome) + modelBuilderCustomization() + }) + + +@VisibleForTesting +fun fetchKotlinBuildScriptModelFor( + importedProjectDir: File, + scriptFile: File?, + connectorForProject: Function +): KotlinBuildScriptModel = + + fetchKotlinBuildScriptModelFor(FetchParameters(importedProjectDir, scriptFile, connectorForProject)) + + +private +data class FetchParameters( + val importedProjectDir: File, + val scriptFile: File?, + val connectorForProject: Function, + val options: List = emptyList(), + val jvmOptions: List = emptyList(), + val correlationId: String = newCorrelationId(), + val modelBuilderCustomization: ModelBuilderCustomization = {} +) - val importedProjectDir = request.projectDir - val scriptFile = request.scriptFile - ?: return fetchKotlinBuildScriptModelFrom(importedProjectDir, request, modelBuilderCustomization) - val effectiveProjectDir = buildSrcProjectDirOf(scriptFile, importedProjectDir) - ?: importedProjectDir +private +fun KotlinBuildScriptModelRequest.toFetchParametersWith(modelBuilderCustomization: ModelBuilderCustomization) = + FetchParameters( + projectDir, + scriptFile, + Function { projectDir -> connectorFor(this).forProjectDirectory(projectDir) }, + options, + jvmOptions, + correlationId, + modelBuilderCustomization + ) + + +private +fun connectorFor(request: KotlinBuildScriptModelRequest): GradleConnector = + GradleConnector.newConnector() + .useGradleFrom(request.gradleInstallation) + .useGradleUserHomeDir(request.gradleUserHome) + - val scriptModel = fetchKotlinBuildScriptModelFrom(effectiveProjectDir, request, modelBuilderCustomization) - if (scriptModel.enclosingScriptProjectDir == null && hasProjectDependentClassPath(scriptFile)) { - val externalProjectRoot = projectRootOf(scriptFile, importedProjectDir) - if (externalProjectRoot != importedProjectDir) { - return fetchKotlinBuildScriptModelFrom(externalProjectRoot, request, modelBuilderCustomization) +private +fun GradleConnector.useGradleFrom(gradleInstallation: GradleInstallation): GradleConnector = + gradleInstallation.run { + when (this) { + is GradleInstallation.Local -> useInstallation(dir) + is GradleInstallation.Remote -> useDistribution(uri) + is GradleInstallation.Version -> useGradleVersion(number) + GradleInstallation.Wrapper -> useBuildDistribution() } } - return scriptModel -} private -fun hasProjectDependentClassPath(scriptFile: File): Boolean = - when (kotlinScriptTypeFor(scriptFile)) { - KotlinScriptType.INIT -> false - else -> true +fun fetchKotlinBuildScriptModelFor(parameters: FetchParameters): KotlinBuildScriptModel { + + if (parameters.scriptFile == null) { + return fetchKotlinBuildScriptModelFrom(parameters.importedProjectDir, parameters) + } + + val effectiveProjectDir = buildSrcProjectDirOf(parameters.scriptFile, parameters.importedProjectDir) + ?: parameters.importedProjectDir + + val scriptModel = fetchKotlinBuildScriptModelFrom(effectiveProjectDir, parameters) + if (scriptModel.enclosingScriptProjectDir == null && hasProjectDependentClassPath(parameters.scriptFile)) { + val externalProjectRoot = projectRootOf(parameters.scriptFile, parameters.importedProjectDir) + if (externalProjectRoot != parameters.importedProjectDir) { + return fetchKotlinBuildScriptModelFrom(externalProjectRoot, parameters) + } } + return scriptModel +} private fun fetchKotlinBuildScriptModelFrom( projectDir: File, - request: KotlinBuildScriptModelRequest, - modelBuilderCustomization: ModelBuilderCustomization + parameters: FetchParameters ): KotlinBuildScriptModel = - projectConnectionFor(request, projectDir).let { connection -> + connectionForProjectDir(projectDir, parameters).let { connection -> @Suppress("ConvertTryFinallyToUseCall") try { - connection.modelBuilderFor(request).apply(modelBuilderCustomization).get() + connection.modelBuilderFor(parameters).apply(parameters.modelBuilderCustomization).get() } finally { connection.close() } @@ -115,20 +170,20 @@ fun fetchKotlinBuildScriptModelFrom( private -fun projectConnectionFor(request: KotlinBuildScriptModelRequest, projectDir: File): ProjectConnection = - connectorFor(request, projectDir).connect() +fun connectionForProjectDir(projectDir: File, parameters: FetchParameters): ProjectConnection = + parameters.connectorForProject.apply(projectDir).connect() private -fun ProjectConnection.modelBuilderFor(request: KotlinBuildScriptModelRequest) = +fun ProjectConnection.modelBuilderFor(parameters: FetchParameters) = model(KotlinBuildScriptModel::class.java).apply { - setJavaHome(request.javaHome) - setJvmArguments(request.jvmOptions + modelSpecificJvmOptions) + setJvmArguments(parameters.jvmOptions + modelSpecificJvmOptions) + forTasks(kotlinBuildScriptModelTask) - val arguments = request.options.toMutableList() - arguments += "-P$kotlinBuildScriptModelCorrelationId=${request.correlationId}" + val arguments = parameters.options.toMutableList() + arguments += "-P$kotlinBuildScriptModelCorrelationId=${parameters.correlationId}" - request.scriptFile?.let { + parameters.scriptFile?.let { arguments += "-P$kotlinBuildScriptModelTarget=${it.canonicalPath}" } @@ -147,10 +202,7 @@ const val kotlinBuildScriptModelTarget = "org.gradle.kotlin.dsl.provider.script" const val kotlinBuildScriptModelCorrelationId = "org.gradle.kotlin.dsl.provider.cid" -private -fun connectorFor(request: KotlinBuildScriptModelRequest, projectDir: File): GradleConnector = - connectorFor(projectDir, request.gradleInstallation) - .useGradleUserHomeDir(request.gradleUserHome) +const val kotlinBuildScriptModelTask = "prepareKotlinBuildScriptModel" private @@ -160,6 +212,14 @@ fun buildSrcProjectDirOf(scriptFile: File, importedProjectDir: File): File? = } +private +fun hasProjectDependentClassPath(scriptFile: File): Boolean = + when (kotlinScriptTypeFor(scriptFile)) { + KotlinScriptType.INIT -> false + else -> true + } + + internal fun projectRootOf(scriptFile: File, importedProjectRoot: File, stopAt: File? = null): File { @@ -184,25 +244,3 @@ fun projectRootOf(scriptFile: File, importedProjectRoot: File, stopAt: File? = n return test(scriptFile.parentFile) } - - -private -fun connectorFor(projectDir: File, gradleInstallation: GradleInstallation): GradleConnector = - GradleConnector - .newConnector() - .forProjectDirectory(projectDir) - .let { connector -> - applyGradleInstallationTo(connector, gradleInstallation) - } - - -private -fun applyGradleInstallationTo(connector: GradleConnector, gradleInstallation: GradleInstallation): GradleConnector = - gradleInstallation.run { - when (this) { - is GradleInstallation.Local -> connector.useInstallation(dir) - is GradleInstallation.Remote -> connector.useDistribution(uri) - is GradleInstallation.Version -> connector.useGradleVersion(number) - GradleInstallation.Wrapper -> connector.useBuildDistribution() - } - } diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/SourceDistributionProvider.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/SourceDistributionProvider.kt index 9dc8f6e28dd8e..10050c3384c37 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/SourceDistributionProvider.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/resolver/SourceDistributionProvider.kt @@ -20,13 +20,12 @@ import org.gradle.api.Project import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.repositories.ArtifactRepository import org.gradle.api.artifacts.repositories.IvyArtifactRepository -import org.gradle.api.artifacts.transform.VariantTransform +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.artifacts.transform.TransformSpec import org.gradle.api.attributes.Attribute - -import org.gradle.kotlin.dsl.create - +import org.gradle.kotlin.dsl.* import java.io.File - import java.lang.Integer.max @@ -40,6 +39,7 @@ class SourceDistributionResolver(val project: Project) : SourceDistributionProvi companion object { val artifactType = Attribute.of("artifactType", String::class.java) val zipType = "zip" + val unzippedDistributionType = "unzipped-distribution" val sourceDirectory = "src-directory" } @@ -69,12 +69,16 @@ class SourceDistributionResolver(val project: Project) : SourceDistributionProvi } private - fun registerTransforms() = - registerTransform { + fun registerTransforms() { + registerTransform { from.attribute(artifactType, zipType) + to.attribute(artifactType, unzippedDistributionType) + } + registerTransform { + from.attribute(artifactType, unzippedDistributionType) to.attribute(artifactType, sourceDirectory) - artifactTransform(ExtractGradleSourcesTransform::class.java) } + } private fun transientConfigurationForSourcesDownload() = @@ -147,8 +151,8 @@ class SourceDistributionResolver(val project: Project) : SourceDistributionProvi } private - fun registerTransform(configure: VariantTransform.() -> Unit) = - dependencies.registerTransform { configure(it) } + inline fun > registerTransform(crossinline configure: TransformSpec.() -> Unit) = + dependencies.registerTransform(T::class.java) { configure(it) } private fun ivy(configure: IvyArtifactRepository.() -> Unit) = diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/KotlinCompiler.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/KotlinCompiler.kt index 4631a1985d83a..c748777c5ab8c 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/KotlinCompiler.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/KotlinCompiler.kt @@ -72,6 +72,24 @@ import java.io.OutputStream import java.io.PrintStream +fun compileKotlinScriptModuleTo( + outputDirectory: File, + moduleName: String, + scriptFiles: Collection, + scriptDef: KotlinScriptDefinition, + classPath: Iterable, + logger: Logger, + pathTranslation: (String) -> String +) = compileKotlinScriptModuleTo( + outputDirectory, + moduleName, + scriptFiles, + scriptDef, + classPath, + LoggingMessageCollector(logger, pathTranslation) +) + + internal fun compileKotlinScriptToDirectory( outputDirectory: File, @@ -79,19 +97,41 @@ fun compileKotlinScriptToDirectory( scriptDef: KotlinScriptDefinition, classPath: List, messageCollector: LoggingMessageCollector -): String = +): String { + + compileKotlinScriptModuleTo( + outputDirectory, + "buildscript", + listOf(scriptFile.path), + scriptDef, + classPath, + messageCollector + ) + + return NameUtils.getScriptNameForFile(scriptFile.name).asString() +} + +private +fun compileKotlinScriptModuleTo( + outputDirectory: File, + moduleName: String, + scriptFiles: Collection, + scriptDef: KotlinScriptDefinition, + classPath: Iterable, + messageCollector: LoggingMessageCollector +) { withRootDisposable { withCompilationExceptionHandler(messageCollector) { val configuration = compilerConfigurationFor(messageCollector).apply { - addKotlinSourceRoot(scriptFile.canonicalPath) put(JVM_TARGET, JVM_1_8) put(RETAIN_OUTPUT_IN_MEMORY, false) put(OUTPUT_DIRECTORY, outputDirectory) - setModuleName("buildscript") + setModuleName(moduleName) addScriptDefinition(scriptDef) + scriptFiles.forEach { addKotlinSourceRoot(it) } classPath.forEach { addJvmClasspathRoot(it) } } val environment = kotlinCoreEnvironmentFor(configuration).apply { @@ -100,10 +140,9 @@ fun compileKotlinScriptToDirectory( compileBunchOfSources(environment) || throw ScriptCompilationException(messageCollector.errors) - - NameUtils.getScriptNameForFile(scriptFile.name).asString() } } +} private diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/KotlinPluginsBlock.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/KotlinPluginsBlock.kt index 666c2ef7236b2..24e97bfe05b55 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/KotlinPluginsBlock.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/KotlinPluginsBlock.kt @@ -20,6 +20,7 @@ import org.gradle.api.Project import org.gradle.api.initialization.dsl.ScriptHandler import org.gradle.kotlin.dsl.ScriptHandlerScope +import org.gradle.kotlin.dsl.support.delegates.ProjectDelegate import org.gradle.plugin.use.PluginDependenciesSpec @@ -41,7 +42,10 @@ abstract class KotlinPluginsBlock(val pluginDependencies: PluginDependenciesSpec abstract class KotlinBuildscriptAndPluginsBlock( private val host: KotlinScriptHost, val pluginDependencies: PluginDependenciesSpec -) : Project by host.target { +) : ProjectDelegate() { + + override val delegate: Project + get() = host.target /** * The [ScriptHandler] for this script. diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ArtifactHandlerDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ArtifactHandlerDelegate.kt new file mode 100644 index 0000000000000..d1de924fc126f --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ArtifactHandlerDelegate.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.api.Action +import org.gradle.api.artifacts.ConfigurablePublishArtifact +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.artifacts.dsl.ArtifactHandler + + +/** + * Facilitates the implementation of the [ArtifactHandler] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class ArtifactHandlerDelegate : ArtifactHandler { + + internal + abstract val delegate: ArtifactHandler + + override fun add(configurationName: String, artifactNotation: Any): PublishArtifact = + delegate.add(configurationName, artifactNotation) + + override fun add(configurationName: String, artifactNotation: Any, configureClosure: Closure): PublishArtifact = + delegate.add(configurationName, artifactNotation, configureClosure) + + override fun add(configurationName: String, artifactNotation: Any, configureAction: Action): PublishArtifact = + delegate.add(configurationName, artifactNotation, configureAction) +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ClientModuleDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ClientModuleDelegate.kt new file mode 100644 index 0000000000000..847fbd4119c3c --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ClientModuleDelegate.kt @@ -0,0 +1,146 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.api.Action +import org.gradle.api.artifacts.ClientModule +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.DependencyArtifact +import org.gradle.api.artifacts.ExcludeRule +import org.gradle.api.artifacts.ExternalDependency +import org.gradle.api.artifacts.ExternalModuleDependency +import org.gradle.api.artifacts.ModuleDependency +import org.gradle.api.artifacts.ModuleVersionSelector +import org.gradle.api.artifacts.ModuleDependencyCapabilitiesHandler +import org.gradle.api.artifacts.ModuleIdentifier +import org.gradle.api.artifacts.ModuleVersionIdentifier +import org.gradle.api.artifacts.MutableVersionConstraint +import org.gradle.api.artifacts.VersionConstraint +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.capabilities.Capability + + +/** + * Facilitates the implementation of the [ClientModule] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class ClientModuleDelegate : ClientModule { + + internal + abstract val delegate: ClientModule + + override fun getGroup(): String = + /** Because this also implements [ModuleVersionSelector.getGroup] it must not return `null` */ + delegate.group ?: "" + + override fun addDependency(dependency: ModuleDependency) = + delegate.addDependency(dependency) + + override fun getName(): String = + delegate.name + + override fun getExcludeRules(): MutableSet = + delegate.excludeRules + + override fun addArtifact(artifact: DependencyArtifact): ModuleDependency = + delegate.addArtifact(artifact) + + override fun artifact(configureClosure: Closure): DependencyArtifact = + delegate.artifact(configureClosure) + + override fun artifact(configureAction: Action): DependencyArtifact = + delegate.artifact(configureAction) + + override fun getAttributes(): AttributeContainer = + delegate.attributes + + override fun capabilities(configureAction: Action): ModuleDependency = + delegate.capabilities(configureAction) + + override fun version(configureAction: Action) = + delegate.version(configureAction) + + override fun getId(): String = + delegate.id + + override fun getTargetConfiguration(): String? = + delegate.targetConfiguration + + override fun copy(): ClientModule = + delegate.copy() + + override fun attributes(configureAction: Action): ModuleDependency = + delegate.attributes(configureAction) + + override fun matchesStrictly(identifier: ModuleVersionIdentifier): Boolean = + delegate.matchesStrictly(identifier) + + override fun isChanging(): Boolean = + delegate.isChanging + + override fun getVersion(): String? = + delegate.version + + override fun isTransitive(): Boolean = + delegate.isTransitive + + override fun setTransitive(transitive: Boolean): ModuleDependency = + delegate.setTransitive(transitive) + + override fun setForce(force: Boolean): ExternalDependency = + delegate.setForce(force) + + override fun contentEquals(dependency: Dependency): Boolean = + delegate.contentEquals(dependency) + + override fun getRequestedCapabilities(): MutableList = + delegate.requestedCapabilities + + override fun getVersionConstraint(): VersionConstraint = + delegate.versionConstraint + + override fun getModule(): ModuleIdentifier = + delegate.module + + override fun getArtifacts(): MutableSet = + delegate.artifacts + + override fun setTargetConfiguration(name: String?) { + delegate.targetConfiguration = name + } + + override fun setChanging(changing: Boolean): ExternalModuleDependency = + delegate.setChanging(changing) + + override fun isForce(): Boolean = + delegate.isForce + + override fun getDependencies(): MutableSet = + delegate.dependencies + + override fun because(reason: String?) = + delegate.because(reason) + + override fun exclude(excludeProperties: Map): ModuleDependency = + delegate.exclude(excludeProperties) + + override fun getReason(): String? = + delegate.reason +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/DependencyConstraintHandlerDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/DependencyConstraintHandlerDelegate.kt new file mode 100644 index 0000000000000..84bf2385351ae --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/DependencyConstraintHandlerDelegate.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import org.gradle.api.Action +import org.gradle.api.artifacts.DependencyConstraint +import org.gradle.api.artifacts.dsl.DependencyConstraintHandler + + +/** + * Facilitates the implementation of the [DependencyConstraintHandler] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class DependencyConstraintHandlerDelegate : DependencyConstraintHandler { + + internal + abstract val delegate: DependencyConstraintHandler + + override fun add(configurationName: String, dependencyConstraintNotation: Any): DependencyConstraint = + delegate.add(configurationName, dependencyConstraintNotation) + + override fun add(configurationName: String, dependencyNotation: Any, configureAction: Action): DependencyConstraint = + delegate.add(configurationName, dependencyNotation, configureAction) + + override fun create(dependencyConstraintNotation: Any): DependencyConstraint = + delegate.create(dependencyConstraintNotation) + + override fun create(dependencyConstraintNotation: Any, configureAction: Action): DependencyConstraint = + delegate.create(dependencyConstraintNotation, configureAction) + + override fun platform(notation: Any): DependencyConstraint = + delegate.platform(notation) + + override fun platform(notation: Any, configureAction: Action): DependencyConstraint = + delegate.platform(notation, configureAction) + + override fun enforcedPlatform(notation: Any): DependencyConstraint = + delegate.enforcedPlatform(notation) + + override fun enforcedPlatform(notation: Any, configureAction: Action): DependencyConstraint = + delegate.enforcedPlatform(notation, configureAction) +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/DependencyHandlerDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/DependencyHandlerDelegate.kt new file mode 100644 index 0000000000000..0c2bddb217c44 --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/DependencyHandlerDelegate.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.api.Action +import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.dsl.ComponentMetadataHandler +import org.gradle.api.artifacts.dsl.ComponentModuleMetadataHandler +import org.gradle.api.artifacts.dsl.DependencyConstraintHandler +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.artifacts.query.ArtifactResolutionQuery +import org.gradle.api.artifacts.transform.TransformAction +import org.gradle.api.artifacts.transform.TransformParameters +import org.gradle.api.artifacts.transform.TransformSpec +import org.gradle.api.artifacts.transform.VariantTransform +import org.gradle.api.artifacts.type.ArtifactTypeContainer +import org.gradle.api.attributes.AttributesSchema + + +/** + * Facilitates the implementation of the [DependencyHandler] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class DependencyHandlerDelegate : DependencyHandler { + + internal + abstract val delegate: DependencyHandler + + override fun add(configurationName: String, dependencyNotation: Any): Dependency? = + delegate.add(configurationName, dependencyNotation) + + override fun add(configurationName: String, dependencyNotation: Any, configureClosure: Closure): Dependency = + delegate.add(configurationName, dependencyNotation, configureClosure) + + override fun create(dependencyNotation: Any): Dependency = + delegate.create(dependencyNotation) + + override fun create(dependencyNotation: Any, configureClosure: Closure): Dependency = + delegate.create(dependencyNotation, configureClosure) + + override fun module(notation: Any): Dependency = + delegate.module(notation) + + override fun module(notation: Any, configureClosure: Closure): Dependency = + delegate.module(notation, configureClosure) + + override fun project(notation: Map): Dependency = + delegate.project(notation) + + override fun gradleApi(): Dependency = + delegate.gradleApi() + + override fun gradleTestKit(): Dependency = + delegate.gradleTestKit() + + override fun localGroovy(): Dependency = + delegate.localGroovy() + + override fun getConstraints(): DependencyConstraintHandler = + delegate.constraints + + override fun constraints(configureAction: Action) = + delegate.constraints(configureAction) + + override fun getComponents(): ComponentMetadataHandler = + delegate.components + + override fun components(configureAction: Action) = + delegate.components(configureAction) + + override fun getModules(): ComponentModuleMetadataHandler = + delegate.modules + + override fun modules(configureAction: Action) = + delegate.modules(configureAction) + + override fun createArtifactResolutionQuery(): ArtifactResolutionQuery = + delegate.createArtifactResolutionQuery() + + override fun attributesSchema(configureAction: Action): AttributesSchema = + delegate.attributesSchema(configureAction) + + override fun getAttributesSchema(): AttributesSchema = + delegate.attributesSchema + + override fun getArtifactTypes(): ArtifactTypeContainer = + delegate.artifactTypes + + override fun artifactTypes(configureAction: Action) = + delegate.artifactTypes(configureAction) + + override fun registerTransform(registrationAction: Action) = + delegate.registerTransform(registrationAction) + + override fun registerTransform(actionType: Class>, registrationAction: Action>) = + delegate.registerTransform(actionType, registrationAction) + + override fun platform(notation: Any): Dependency = + delegate.platform(notation) + + override fun platform(notation: Any, configureAction: Action): Dependency = + delegate.platform(notation, configureAction) + + override fun enforcedPlatform(notation: Any): Dependency = + delegate.enforcedPlatform(notation) + + override fun enforcedPlatform(notation: Any, configureAction: Action): Dependency = + delegate.enforcedPlatform(notation, configureAction) +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/GradleDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/GradleDelegate.kt new file mode 100644 index 0000000000000..d757351cdcbf7 --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/GradleDelegate.kt @@ -0,0 +1,163 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.BuildListener +import org.gradle.BuildResult +import org.gradle.StartParameter +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.ProjectEvaluationListener +import org.gradle.api.execution.TaskExecutionGraph +import org.gradle.api.initialization.IncludedBuild +import org.gradle.api.initialization.Settings +import org.gradle.api.invocation.Gradle +import org.gradle.api.plugins.ObjectConfigurationAction +import org.gradle.api.plugins.PluginContainer +import org.gradle.api.plugins.PluginManager + +import java.io.File + + +/** + * Facilitates the implementation of the [Gradle] interface by delegation via subclassing. + * + * So we can avoid Kotlin's [implementation by delegation](https://kotlinlang.org/docs/reference/delegation.html#implementation-by-delegation) + * until all required interfaces have been compiled with Java 8 parameter names (otherwise parameter names + * are lost in the exposed implementation). + * + * Once the required interfaces are compiled with Java 8 parameter names these classes can be removed in favor + * of Kotlin's implementation by delegation. + */ +abstract class GradleDelegate : Gradle { + + internal + abstract val delegate: Gradle + + override fun getGradleVersion(): String = + delegate.gradleVersion + + override fun getGradleUserHomeDir(): File = + delegate.gradleUserHomeDir + + override fun getGradleHomeDir(): File? = + delegate.gradleHomeDir + + override fun getParent(): Gradle? = + delegate.parent + + override fun getRootProject(): Project = + delegate.rootProject + + override fun rootProject(action: Action) = + delegate.rootProject(action) + + override fun allprojects(action: Action) = + delegate.allprojects(action) + + override fun getTaskGraph(): TaskExecutionGraph = + delegate.taskGraph + + override fun getStartParameter(): StartParameter = + delegate.startParameter + + override fun addProjectEvaluationListener(listener: ProjectEvaluationListener): ProjectEvaluationListener = + delegate.addProjectEvaluationListener(listener) + + override fun removeProjectEvaluationListener(listener: ProjectEvaluationListener) = + delegate.removeProjectEvaluationListener(listener) + + override fun beforeProject(closure: Closure) = + delegate.beforeProject(closure) + + override fun beforeProject(action: Action) = + delegate.beforeProject(action) + + override fun afterProject(closure: Closure) = + delegate.afterProject(closure) + + override fun afterProject(action: Action) = + delegate.afterProject(action) + + override fun buildStarted(closure: Closure) = + delegate.buildStarted(closure) + + override fun buildStarted(action: Action) = + delegate.buildStarted(action) + + override fun settingsEvaluated(closure: Closure) = + delegate.settingsEvaluated(closure) + + override fun settingsEvaluated(action: Action) = + delegate.settingsEvaluated(action) + + override fun projectsLoaded(closure: Closure) = + delegate.projectsLoaded(closure) + + override fun projectsLoaded(action: Action) = + delegate.projectsLoaded(action) + + override fun projectsEvaluated(closure: Closure) = + delegate.projectsEvaluated(closure) + + override fun projectsEvaluated(action: Action) = + delegate.projectsEvaluated(action) + + override fun buildFinished(closure: Closure) = + delegate.buildFinished(closure) + + override fun buildFinished(action: Action) = + delegate.buildFinished(action) + + override fun addBuildListener(buildListener: BuildListener) = + delegate.addBuildListener(buildListener) + + override fun addListener(listener: Any) = + delegate.addListener(listener) + + override fun removeListener(listener: Any) = + delegate.removeListener(listener) + + override fun useLogger(logger: Any) = + delegate.useLogger(logger) + + override fun getGradle(): Gradle = + delegate.gradle + + override fun getIncludedBuilds(): MutableCollection = + delegate.includedBuilds + + override fun includedBuild(name: String): IncludedBuild = + delegate.includedBuild(name) + + override fun getPlugins(): PluginContainer = + delegate.plugins + + override fun apply(closure: Closure) = + delegate.apply(closure) + + override fun apply(action: Action) = + delegate.apply(action) + + override fun apply(options: Map) = + delegate.apply(options) + + override fun getPluginManager(): PluginManager = + delegate.pluginManager +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/NamedDomainObjectContainerDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/NamedDomainObjectContainerDelegate.kt new file mode 100644 index 0000000000000..d53d8b03f4ed3 --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/NamedDomainObjectContainerDelegate.kt @@ -0,0 +1,195 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.api.Action +import org.gradle.api.DomainObjectCollection +import org.gradle.api.NamedDomainObjectCollectionSchema +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.NamedDomainObjectProvider +import org.gradle.api.NamedDomainObjectSet +import org.gradle.api.Namer +import org.gradle.api.Rule +import org.gradle.api.provider.Provider +import org.gradle.api.specs.Spec + +import java.util.SortedMap +import java.util.SortedSet + + +/** + * Facilitates the implementation of the [NamedDomainObjectContainer] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class NamedDomainObjectContainerDelegate : NamedDomainObjectContainer { + + internal + abstract val delegate: NamedDomainObjectContainer + + override fun contains(element: T): Boolean = + delegate.contains(element) + + override fun addAll(elements: Collection): Boolean = + delegate.addAll(elements) + + override fun matching(spec: Spec): NamedDomainObjectSet = + delegate.matching(spec) + + override fun matching(spec: Closure): NamedDomainObjectSet = + delegate.matching(spec) + + override fun clear() = + delegate.clear() + + override fun addRule(rule: Rule): Rule = + delegate.addRule(rule) + + override fun addRule(description: String, ruleAction: Closure): Rule = + delegate.addRule(description, ruleAction) + + override fun addRule(description: String, ruleAction: Action): Rule = + delegate.addRule(description, ruleAction) + + override fun configure(configureClosure: Closure): NamedDomainObjectContainer = + delegate.configure(configureClosure) + + override fun addAllLater(provider: Provider>) = + delegate.addAllLater(provider) + + override fun create(name: String): T = + delegate.create(name) + + override fun create(name: String, configureClosure: Closure): T = + delegate.create(name, configureClosure) + + override fun create(name: String, configureAction: Action): T = + delegate.create(name, configureAction) + + override fun removeAll(elements: Collection): Boolean = + delegate.removeAll(elements) + + override fun add(element: T): Boolean = + delegate.add(element) + + override fun all(action: Action) = + delegate.all(action) + + override fun all(action: Closure) = + delegate.all(action) + + override fun register(name: String, configurationAction: Action): NamedDomainObjectProvider = + delegate.register(name, configurationAction) + + override fun register(name: String): NamedDomainObjectProvider = + delegate.register(name) + + override fun iterator(): MutableIterator = + delegate.iterator() + + override fun getNamer(): Namer = + delegate.namer + + override fun getRules(): MutableList = + delegate.rules + + override fun named(name: String): NamedDomainObjectProvider = + delegate.named(name) + + override fun named(name: String, configurationAction: Action): NamedDomainObjectProvider = + delegate.named(name, configurationAction) + + override fun named(name: String, type: Class): NamedDomainObjectProvider = + delegate.named(name, type) + + override fun getCollectionSchema(): NamedDomainObjectCollectionSchema = + delegate.collectionSchema + + override fun whenObjectRemoved(action: Action): Action = + delegate.whenObjectRemoved(action) + + override fun whenObjectRemoved(action: Closure) = + delegate.whenObjectRemoved(action) + + override fun findAll(spec: Closure): MutableSet = + delegate.findAll(spec) + + override fun addLater(provider: Provider) = + delegate.addLater(provider) + + override fun containsAll(elements: Collection): Boolean = + delegate.containsAll(elements) + + override fun isEmpty(): Boolean = + delegate.isEmpty() + + override fun remove(element: T): Boolean = + delegate.remove(element) + + override fun getAsMap(): SortedMap = + delegate.asMap + + override fun getNames(): SortedSet = + delegate.names + + override fun getByName(name: String): T = + delegate.getByName(name) + + override fun getByName(name: String, configureClosure: Closure): T = + delegate.getByName(name, configureClosure) + + override fun getByName(name: String, configureAction: Action): T = + delegate.getByName(name, configureAction) + + override fun withType(type: Class, configureClosure: Closure): DomainObjectCollection = + delegate.withType(type, configureClosure) + + override fun configureEach(action: Action) = + delegate.configureEach(action) + + override fun maybeCreate(name: String): T = + delegate.maybeCreate(name) + + override fun withType(type: Class): NamedDomainObjectSet = + delegate.withType(type) + + override fun withType(type: Class, configureAction: Action): DomainObjectCollection = + delegate.withType(type, configureAction) + + override fun findByName(name: String): T? = + delegate.findByName(name) + + override fun whenObjectAdded(action: Action): Action = + delegate.whenObjectAdded(action) + + override fun whenObjectAdded(action: Closure) = + delegate.whenObjectAdded(action) + + override fun retainAll(elements: Collection): Boolean = + delegate.retainAll(elements) + + override fun getAt(name: String): T = + delegate.getAt(name) + + override fun named(name: String, type: Class, configurationAction: Action): NamedDomainObjectProvider = + delegate.named(name, type, configurationAction) + + override val size: Int + get() = delegate.size +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ProjectDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ProjectDelegate.kt new file mode 100644 index 0000000000000..b95d97bbec0ac --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ProjectDelegate.kt @@ -0,0 +1,461 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.api.Action +import org.gradle.api.AntBuilder +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.NamedDomainObjectFactory +import org.gradle.api.PathValidation +import org.gradle.api.Project +import org.gradle.api.ProjectState +import org.gradle.api.Task +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.artifacts.dsl.ArtifactHandler +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.artifacts.dsl.DependencyLockingHandler +import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.component.SoftwareComponentContainer +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.CopySpec +import org.gradle.api.file.DeleteSpec +import org.gradle.api.file.FileTree +import org.gradle.api.file.ProjectLayout +import org.gradle.api.initialization.dsl.ScriptHandler +import org.gradle.api.invocation.Gradle +import org.gradle.api.logging.Logger +import org.gradle.api.logging.LoggingManager +import org.gradle.api.model.ObjectFactory +import org.gradle.api.plugins.Convention +import org.gradle.api.plugins.ExtensionContainer +import org.gradle.api.plugins.ObjectConfigurationAction +import org.gradle.api.plugins.PluginContainer +import org.gradle.api.plugins.PluginManager +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.resources.ResourceHandler +import org.gradle.api.tasks.TaskContainer +import org.gradle.api.tasks.WorkResult +import org.gradle.normalization.InputNormalizationHandler +import org.gradle.process.ExecResult +import org.gradle.process.ExecSpec +import org.gradle.process.JavaExecSpec + +import java.io.File + +import java.net.URI + +import java.util.concurrent.Callable + + +/** + * Facilitates the implementation of the [Project] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class ProjectDelegate() : Project { + + internal + abstract val delegate: Project + + override fun getGroup(): Any = + delegate.group + + override fun afterEvaluate(action: Action) = + delegate.afterEvaluate(action) + + override fun afterEvaluate(closure: Closure<*>) = + delegate.afterEvaluate(closure) + + override fun getDefaultTasks(): MutableList = + delegate.defaultTasks + + override fun getConvention(): Convention = + delegate.convention + + override fun getLogger(): Logger = + delegate.logger + + override fun getBuildDir(): File = + delegate.buildDir + + override fun getAnt(): AntBuilder = + delegate.ant + + override fun getVersion(): Any = + delegate.version + + override fun getRootProject(): Project = + delegate.rootProject + + override fun depthCompare(otherProject: Project): Int = + delegate.depthCompare(otherProject) + + override fun getGradle(): Gradle = + delegate.gradle + + override fun getAllTasks(recursive: Boolean): MutableMap> = + delegate.getAllTasks(recursive) + + override fun uri(path: Any): URI = + delegate.uri(path) + + override fun copySpec(closure: Closure<*>): CopySpec = + delegate.copySpec(closure) + + override fun copySpec(action: Action): CopySpec = + delegate.copySpec(action) + + override fun copySpec(): CopySpec = + delegate.copySpec() + + override fun relativePath(path: Any): String = + delegate.relativePath(path) + + override fun setProperty(name: String, value: Any?) = + delegate.setProperty(name, value) + + override fun beforeEvaluate(action: Action) = + delegate.beforeEvaluate(action) + + override fun beforeEvaluate(closure: Closure<*>) = + delegate.beforeEvaluate(closure) + + override fun property(propertyName: String): Any? = + delegate.property(propertyName) + + override fun buildscript(configureClosure: Closure<*>) = + delegate.buildscript(configureClosure) + + override fun getProject(): Project = + delegate.project + + override fun dependencies(configureClosure: Closure<*>) = + delegate.dependencies(configureClosure) + + override fun getPath(): String = + delegate.path + + override fun zipTree(zipPath: Any): FileTree = + delegate.zipTree(zipPath) + + override fun allprojects(action: Action) = + delegate.allprojects(action) + + override fun allprojects(configureClosure: Closure<*>) = + delegate.allprojects(configureClosure) + + override fun container(type: Class): NamedDomainObjectContainer = + delegate.container(type) + + override fun container(type: Class, factory: NamedDomainObjectFactory): NamedDomainObjectContainer = + delegate.container(type, factory) + + override fun container(type: Class, factoryClosure: Closure<*>): NamedDomainObjectContainer = + delegate.container(type, factoryClosure) + + override fun repositories(configureClosure: Closure<*>) = + delegate.repositories(configureClosure) + + override fun evaluationDependsOnChildren() = + delegate.evaluationDependsOnChildren() + + override fun configure(`object`: Any, configureClosure: Closure<*>): Any = + delegate.configure(`object`, configureClosure) + + override fun configure(objects: Iterable<*>, configureClosure: Closure<*>): Iterable<*> = + delegate.configure(objects, configureClosure) + + override fun configure(objects: Iterable, configureAction: Action): Iterable = + delegate.configure(objects, configureAction) + + override fun exec(closure: Closure<*>): ExecResult = + delegate.exec(closure) + + override fun exec(action: Action): ExecResult = + delegate.exec(action) + + override fun sync(action: Action): WorkResult = + delegate.sync(action) + + override fun configurations(configureClosure: Closure<*>) = + delegate.configurations(configureClosure) + + override fun getExtensions(): ExtensionContainer = + delegate.extensions + + override fun getProperties(): MutableMap = + delegate.properties + + override fun absoluteProjectPath(path: String): String = + delegate.absoluteProjectPath(path) + + override fun getProjectDir(): File = + delegate.projectDir + + override fun files(vararg paths: Any?): ConfigurableFileCollection = + delegate.files(*paths) + + override fun files(paths: Any, configureClosure: Closure<*>): ConfigurableFileCollection = + delegate.files(paths, configureClosure) + + override fun files(paths: Any, configureAction: Action): ConfigurableFileCollection = + delegate.files(paths, configureAction) + + override fun hasProperty(propertyName: String): Boolean = + delegate.hasProperty(propertyName) + + override fun getState(): ProjectState = + delegate.state + + override fun getComponents(): SoftwareComponentContainer = + delegate.components + + override fun setBuildDir(path: File) { + delegate.buildDir = path + } + + override fun setBuildDir(path: Any) = + delegate.setBuildDir(path) + + override fun defaultTasks(vararg defaultTasks: String?) = + delegate.defaultTasks(*defaultTasks) + + override fun compareTo(other: Project?): Int = + delegate.compareTo(other) + + override fun artifacts(configureClosure: Closure<*>) = + delegate.artifacts(configureClosure) + + override fun artifacts(configureAction: Action) = + delegate.artifacts(configureAction) + + override fun getRootDir(): File = + delegate.rootDir + + override fun getDependencyLocking(): DependencyLockingHandler = + delegate.dependencyLocking + + override fun provider(value: Callable): Provider = + delegate.provider(value) + + override fun findProperty(propertyName: String): Any? = + delegate.findProperty(propertyName) + + override fun getDependencies(): DependencyHandler = + delegate.dependencies + + override fun getResources(): ResourceHandler = + delegate.resources + + override fun setDefaultTasks(defaultTasks: MutableList) { + delegate.defaultTasks = defaultTasks + } + + override fun normalization(configuration: Action) = + delegate.normalization(configuration) + + override fun project(path: String): Project = + delegate.project(path) + + override fun project(path: String, configureClosure: Closure<*>): Project = + delegate.project(path, configureClosure) + + override fun project(path: String, configureAction: Action): Project = + delegate.project(path, configureAction) + + override fun task(name: String): Task = + delegate.task(name) + + override fun task(args: Map, name: String): Task = + delegate.task(args, name) + + override fun task(args: Map, name: String, configureClosure: Closure<*>): Task = + delegate.task(args, name, configureClosure) + + override fun task(name: String, configureClosure: Closure<*>): Task = + delegate.task(name, configureClosure) + + override fun task(name: String, configureAction: Action): Task = + delegate.task(name, configureAction) + + override fun copy(closure: Closure<*>): WorkResult = + delegate.copy(closure) + + override fun copy(action: Action): WorkResult = + delegate.copy(action) + + override fun getDescription(): String? = + delegate.description + + override fun subprojects(action: Action) = + delegate.subprojects(action) + + override fun subprojects(configureClosure: Closure<*>) = + delegate.subprojects(configureClosure) + + override fun getBuildscript(): ScriptHandler = + delegate.buildscript + + override fun getStatus(): Any = + delegate.status + + override fun mkdir(path: Any): File = + delegate.mkdir(path) + + override fun setStatus(status: Any) { + delegate.status = status + } + + override fun getConfigurations(): ConfigurationContainer = + delegate.configurations + + override fun getArtifacts(): ArtifactHandler = + delegate.artifacts + + override fun setDescription(description: String?) { + delegate.description = description + } + + override fun getLayout(): ProjectLayout = + delegate.layout + + override fun apply(closure: Closure<*>) = + delegate.apply(closure) + + override fun apply(action: Action) = + delegate.apply(action) + + override fun apply(options: Map) = + delegate.apply(options) + + override fun evaluationDependsOn(path: String): Project = + delegate.evaluationDependsOn(path) + + override fun javaexec(closure: Closure<*>): ExecResult = + delegate.javaexec(closure) + + override fun javaexec(action: Action): ExecResult = + delegate.javaexec(action) + + override fun getChildProjects(): MutableMap = + delegate.childProjects + + override fun getLogging(): LoggingManager = + delegate.logging + + override fun getTasks(): TaskContainer = + delegate.tasks + + override fun getName(): String = + delegate.name + + override fun file(path: Any): File = + delegate.file(path) + + override fun file(path: Any, validation: PathValidation): File = + delegate.file(path, validation) + + override fun findProject(path: String): Project? = + delegate.findProject(path) + + override fun getPlugins(): PluginContainer = + delegate.plugins + + override fun ant(configureClosure: Closure<*>): AntBuilder = + delegate.ant(configureClosure) + + override fun ant(configureAction: Action): AntBuilder = + delegate.ant(configureAction) + + override fun getAllprojects(): MutableSet = + delegate.allprojects + + override fun createAntBuilder(): AntBuilder = + delegate.createAntBuilder() + + override fun getObjects(): ObjectFactory = + delegate.objects + + override fun dependencyLocking(configuration: Action) = + delegate.dependencyLocking(configuration) + + override fun tarTree(tarPath: Any): FileTree = + delegate.tarTree(tarPath) + + override fun delete(vararg paths: Any?): Boolean = + delegate.delete(*paths) + + override fun delete(action: Action): WorkResult = + delegate.delete(action) + + override fun getRepositories(): RepositoryHandler = + delegate.repositories + + override fun getTasksByName(name: String, recursive: Boolean): MutableSet = + delegate.getTasksByName(name, recursive) + + override fun getParent(): Project? = + delegate.parent + + override fun getDisplayName(): String = + delegate.displayName + + override fun relativeProjectPath(path: String): String = + delegate.relativeProjectPath(path) + + override fun getPluginManager(): PluginManager = + delegate.pluginManager + + override fun setGroup(group: Any) { + delegate.group = group + } + + override fun fileTree(baseDir: Any): ConfigurableFileTree = + delegate.fileTree(baseDir) + + override fun fileTree(baseDir: Any, configureClosure: Closure<*>): ConfigurableFileTree = + delegate.fileTree(baseDir, configureClosure) + + override fun fileTree(baseDir: Any, configureAction: Action): ConfigurableFileTree = + delegate.fileTree(baseDir, configureAction) + + override fun fileTree(args: Map): ConfigurableFileTree = + delegate.fileTree(args) + + override fun getNormalization(): InputNormalizationHandler = + delegate.normalization + + override fun setVersion(version: Any) { + delegate.version = version + } + + override fun getDepth(): Int = + delegate.depth + + override fun getProviders(): ProviderFactory = + delegate.providers + + override fun getSubprojects(): MutableSet = + delegate.subprojects + + override fun getBuildFile(): File = + delegate.buildFile +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ScriptHandlerDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ScriptHandlerDelegate.kt new file mode 100644 index 0000000000000..6f66792fbbda4 --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/ScriptHandlerDelegate.kt @@ -0,0 +1,64 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.artifacts.dsl.DependencyHandler +import org.gradle.api.artifacts.dsl.RepositoryHandler +import org.gradle.api.initialization.dsl.ScriptHandler + +import java.io.File + +import java.net.URI + + +/** + * Facilitates the implementation of the [ScriptHandler] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class ScriptHandlerDelegate : ScriptHandler { + + internal + abstract val delegate: ScriptHandler + + override fun getSourceFile(): File? = + delegate.sourceFile + + override fun getSourceURI(): URI? = + delegate.sourceURI + + override fun getRepositories(): RepositoryHandler = + delegate.repositories + + override fun repositories(configureClosure: Closure) = + delegate.repositories(configureClosure) + + override fun getDependencies(): DependencyHandler = + delegate.dependencies + + override fun dependencies(configureClosure: Closure) = + delegate.dependencies(configureClosure) + + override fun getConfigurations(): ConfigurationContainer = + delegate.configurations + + override fun getClassLoader(): ClassLoader = + delegate.classLoader +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/SettingsDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/SettingsDelegate.kt new file mode 100644 index 0000000000000..7d785b00feb2a --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/SettingsDelegate.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.StartParameter +import org.gradle.api.Action +import org.gradle.api.initialization.ConfigurableIncludedBuild +import org.gradle.api.initialization.ProjectDescriptor +import org.gradle.api.initialization.Settings +import org.gradle.api.initialization.dsl.ScriptHandler +import org.gradle.api.invocation.Gradle +import org.gradle.api.plugins.ExtensionContainer +import org.gradle.api.plugins.ObjectConfigurationAction +import org.gradle.api.plugins.PluginContainer +import org.gradle.api.plugins.PluginManager +import org.gradle.caching.configuration.BuildCacheConfiguration +import org.gradle.plugin.management.PluginManagementSpec +import org.gradle.vcs.SourceControl + +import java.io.File + + +/** + * Facilitates the implementation of the [Settings] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class SettingsDelegate : Settings { + + internal + abstract val delegate: Settings + + override fun getSettings(): Settings = + delegate.settings + + override fun getBuildscript(): ScriptHandler = + delegate.buildscript + + override fun getSettingsDir(): File = + delegate.settingsDir + + override fun getRootDir(): File = + delegate.rootDir + + override fun getRootProject(): ProjectDescriptor = + delegate.rootProject + + override fun project(path: String): ProjectDescriptor = + delegate.project(path) + + override fun findProject(path: String): ProjectDescriptor? = + delegate.findProject(path) + + override fun project(projectDir: File): ProjectDescriptor = + delegate.project(projectDir) + + override fun findProject(projectDir: File): ProjectDescriptor? = + delegate.findProject(projectDir) + + override fun getGradle(): Gradle = + delegate.gradle + + override fun includeBuild(rootProject: Any) = + delegate.includeBuild(rootProject) + + override fun includeBuild(rootProject: Any, configuration: Action) = + delegate.includeBuild(rootProject, configuration) + + override fun enableFeaturePreview(name: String) = + delegate.enableFeaturePreview(name) + + override fun getExtensions(): ExtensionContainer = + delegate.extensions + + override fun getBuildCache(): BuildCacheConfiguration = + delegate.buildCache + + override fun pluginManagement(pluginManagementSpec: Action) = + delegate.pluginManagement(pluginManagementSpec) + + override fun getPluginManagement(): PluginManagementSpec = + delegate.pluginManagement + + override fun sourceControl(configuration: Action) = + delegate.sourceControl(configuration) + + override fun getPlugins(): PluginContainer = + delegate.plugins + + override fun apply(closure: Closure) = + delegate.apply(closure) + + override fun apply(action: Action) = + delegate.apply(action) + + override fun apply(options: Map) = + delegate.apply(options) + + override fun getPluginManager(): PluginManager = + delegate.pluginManager + + override fun include(vararg projectPaths: String?) = + delegate.include(*projectPaths) + + override fun includeFlat(vararg projectNames: String?) = + delegate.includeFlat(*projectNames) + + override fun getStartParameter(): StartParameter = + delegate.startParameter + + override fun buildCache(action: Action) = + delegate.buildCache(action) + + override fun getSourceControl(): SourceControl = + delegate.sourceControl +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/TaskContainerDelegate.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/TaskContainerDelegate.kt new file mode 100644 index 0000000000000..e347d662d0fd9 --- /dev/null +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/delegates/TaskContainerDelegate.kt @@ -0,0 +1,245 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.kotlin.dsl.support.delegates + +import groovy.lang.Closure + +import org.gradle.api.Action +import org.gradle.api.DomainObjectCollection +import org.gradle.api.NamedDomainObjectCollectionSchema +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Namer +import org.gradle.api.Rule +import org.gradle.api.Task +import org.gradle.api.provider.Provider +import org.gradle.api.specs.Spec +import org.gradle.api.tasks.TaskCollection +import org.gradle.api.tasks.TaskContainer +import org.gradle.api.tasks.TaskProvider + +import java.util.SortedMap +import java.util.SortedSet + + +/** + * Facilitates the implementation of the [TaskContainer] interface by delegation via subclassing. + * + * See [GradleDelegate] for why this is currently necessary. + */ +abstract class TaskContainerDelegate : TaskContainer { + + internal + abstract val delegate: TaskContainer + + override fun contains(element: Task): Boolean = + delegate.contains(element) + + override fun addAll(elements: Collection): Boolean = + delegate.addAll(elements) + + override fun matching(spec: Spec): TaskCollection = + delegate.matching(spec) + + override fun matching(closure: Closure): TaskCollection = + delegate.matching(closure) + + override fun clear() = + delegate.clear() + + override fun addRule(rule: Rule): Rule = + delegate.addRule(rule) + + override fun addRule(description: String, ruleAction: Closure): Rule = + delegate.addRule(description, ruleAction) + + override fun addRule(description: String, ruleAction: Action): Rule = + delegate.addRule(description, ruleAction) + + override fun configure(configureClosure: Closure): NamedDomainObjectContainer = + delegate.configure(configureClosure) + + override fun addAllLater(provider: Provider>) = + delegate.addAllLater(provider) + + override fun create(options: Map): Task = + delegate.create(options) + + override fun create(options: Map, configureClosure: Closure): Task = + delegate.create(options, configureClosure) + + override fun create(name: String, configureClosure: Closure): Task = + delegate.create(name, configureClosure) + + override fun create(name: String): Task = + delegate.create(name) + + override fun create(name: String, type: Class): T = + delegate.create(name, type) + + override fun create(name: String, type: Class, vararg constructorArgs: Any?): T = + delegate.create(name, type, *constructorArgs) + + override fun create(name: String, type: Class, configuration: Action): T = + delegate.create(name, type, configuration) + + override fun create(name: String, configureAction: Action): Task = + delegate.create(name, configureAction) + + override fun whenTaskAdded(action: Action): Action = + delegate.whenTaskAdded(action) + + override fun whenTaskAdded(closure: Closure) = + delegate.whenTaskAdded(closure) + + override fun removeAll(elements: Collection): Boolean = + delegate.removeAll(elements) + + override fun getByPath(path: String): Task = + delegate.getByPath(path) + + override fun add(element: Task): Boolean = + delegate.add(element) + + override fun all(action: Action) = + delegate.all(action) + + override fun all(action: Closure) = + delegate.all(action) + + override fun register(name: String, configurationAction: Action): TaskProvider = + delegate.register(name, configurationAction) + + override fun register(name: String, type: Class, configurationAction: Action): TaskProvider = + delegate.register(name, type, configurationAction) + + override fun register(name: String, type: Class): TaskProvider = + delegate.register(name, type) + + override fun register(name: String, type: Class, vararg constructorArgs: Any?): TaskProvider = + delegate.register(name, type, *constructorArgs) + + override fun register(name: String): TaskProvider = + delegate.register(name) + + override fun replace(name: String): Task = + delegate.replace(name) + + override fun replace(name: String, type: Class): T = + delegate.replace(name, type) + + override fun iterator(): MutableIterator = + delegate.iterator() + + override fun named(name: String): TaskProvider = + delegate.named(name) + + override fun named(name: String, configurationAction: Action): TaskProvider = + delegate.named(name, configurationAction) + + override fun named(name: String, type: Class): TaskProvider = + delegate.named(name, type) + + override fun named(name: String, type: Class, configurationAction: Action): TaskProvider = + delegate.named(name, type, configurationAction) + + override fun getNamer(): Namer = + delegate.namer + + override fun getRules(): MutableList = + delegate.rules + + override fun getCollectionSchema(): NamedDomainObjectCollectionSchema = + delegate.collectionSchema + + override fun whenObjectRemoved(action: Action): Action = + delegate.whenObjectRemoved(action) + + override fun whenObjectRemoved(action: Closure) = + delegate.whenObjectRemoved(action) + + override fun findAll(spec: Closure): MutableSet = + delegate.findAll(spec) + + override fun addLater(provider: Provider) = + delegate.addLater(provider) + + override fun containsAll(elements: Collection): Boolean = + delegate.containsAll(elements) + + override fun isEmpty(): Boolean = + delegate.isEmpty() + + override fun containerWithType(type: Class): NamedDomainObjectContainer = + delegate.containerWithType(type) + + override fun getByName(name: String, configureClosure: Closure): Task = + delegate.getByName(name, configureClosure) + + override fun getByName(name: String): Task = + delegate.getByName(name) + + override fun getByName(name: String, configureAction: Action): Task = + delegate.getByName(name, configureAction) + + override fun configureEach(action: Action) = + delegate.configureEach(action) + + override fun maybeCreate(name: String, type: Class): U = + delegate.maybeCreate(name, type) + + override fun maybeCreate(name: String): Task = + delegate.maybeCreate(name) + + override fun findByPath(path: String): Task? = + delegate.findByPath(path) + + override fun withType(type: Class): TaskCollection = + delegate.withType(type) + + override fun withType(type: Class, configureAction: Action): DomainObjectCollection = + delegate.withType(type, configureAction) + + override fun withType(type: Class, configureClosure: Closure): DomainObjectCollection = + delegate.withType(type, configureClosure) + + override fun findByName(name: String): Task? = + delegate.findByName(name) + + override fun whenObjectAdded(action: Action): Action = + delegate.whenObjectAdded(action) + + override fun whenObjectAdded(action: Closure) = + delegate.whenObjectAdded(action) + + override fun retainAll(elements: Collection): Boolean = + delegate.retainAll(elements) + + override fun getAsMap(): SortedMap = + delegate.asMap + + override fun getNames(): SortedSet = + delegate.names + + override fun getAt(name: String): Task = + delegate.getAt(name) + + override val size: Int + get() = delegate.size + + override fun remove(element: Task): Boolean = + delegate.remove(element) +} diff --git a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/zip.kt b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/zip.kt index 9e96e582c9a64..d6c3bdf1b0b5e 100644 --- a/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/zip.kt +++ b/subprojects/kotlin-dsl/src/main/kotlin/org/gradle/kotlin/dsl/support/zip.kt @@ -46,9 +46,9 @@ fun File.walkReproducibly(): Sequence = sequence { while (directories.isNotEmpty()) { val subDirectories = mutableListOf() directories.forEach { dir -> - dir.listFiles()?.sortedBy(fileName)?.partition { it.isDirectory }?.also { (childDirectories, childFiles) -> + dir.listFiles()?.sortedBy(fileName)?.partition { it.isDirectory }?.let { (childDirectories, childFiles) -> yieldAll(childFiles) - childDirectories.also { + childDirectories.let { yieldAll(it) subDirectories.addAll(it) } diff --git a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/DependencyHandlerExtensionsTest.kt b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/DependencyHandlerExtensionsTest.kt index cd5c67be61aff..6351e9278740c 100644 --- a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/DependencyHandlerExtensionsTest.kt +++ b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/DependencyHandlerExtensionsTest.kt @@ -60,10 +60,11 @@ class DependencyHandlerExtensionsTest { @Test fun `given group and module, 'exclude' extension will build corresponding map`() { - val dependencies = DependencyHandlerScope.of(newDependencyHandlerMock()) + val dependencyHandlerMock = newDependencyHandlerMock() + val dependencies = DependencyHandlerScope.of(dependencyHandlerMock) val dependency: ExternalModuleDependency = mock() val events = mutableListOf() - whenever(dependencies.create("dependency")).then { + whenever(dependencyHandlerMock.create("dependency")).then { events.add("created") dependency } @@ -71,7 +72,7 @@ class DependencyHandlerExtensionsTest { events.add("configured") dependency } - whenever(dependencies.add("configuration", dependency)).then { + whenever(dependencyHandlerMock.add("configuration", dependency)).then { events.add("added") dependency } @@ -95,15 +96,16 @@ class DependencyHandlerExtensionsTest { @Test fun `given path and configuration, 'project' extension will build corresponding map`() { - val dependencies = DependencyHandlerScope.of(newDependencyHandlerMock()) + val dependencyHandlerMock = newDependencyHandlerMock() + val dependencies = DependencyHandlerScope.of(dependencyHandlerMock) val dependency: ProjectDependency = mock() val events = mutableListOf() val expectedProjectMap = mapOf("path" to ":project", "configuration" to "default") - whenever(dependencies.project(expectedProjectMap)).then { + whenever(dependencyHandlerMock.project(expectedProjectMap)).then { events.add("created") dependency } - whenever(dependencies.add("configuration", dependency)).then { + whenever(dependencyHandlerMock.add("configuration", dependency)).then { events.add("added") dependency } @@ -161,7 +163,9 @@ class DependencyHandlerExtensionsTest { @Test fun `client module configuration`() { - val clientModule = mock() + val clientModule = mock { + on { setTransitive(any()) }.thenAnswer { it.mock } + } val commonsCliDependency = mock(name = "commonsCliDependency") diff --git a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/ScriptHandlerScopeTest.kt b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/ScriptHandlerScopeTest.kt index d80d103f32143..a65e29edcd660 100644 --- a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/ScriptHandlerScopeTest.kt +++ b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/ScriptHandlerScopeTest.kt @@ -28,9 +28,7 @@ import org.gradle.api.Action import org.gradle.api.artifacts.DependencyConstraint import org.gradle.api.artifacts.ExternalModuleDependency import org.gradle.api.artifacts.dsl.DependencyConstraintHandler -import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.api.initialization.dsl.ScriptHandler -import org.gradle.api.plugins.ExtensionAware import org.gradle.kotlin.dsl.support.configureWith @@ -43,7 +41,7 @@ class ScriptHandlerScopeTest { fun `can exclude modules from classpath dependency`() { val dependency = mock() - val dependencies = mock(arrayOf(ExtensionAware::class)) { + val dependencies = newDependencyHandlerMock { on { create("...") } doReturn dependency } val buildscript = mock { diff --git a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/TaskContainerExtensionsTest.kt b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/TaskContainerExtensionsTest.kt index a693eb182a9e3..3e024507de86d 100644 --- a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/TaskContainerExtensionsTest.kt +++ b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/TaskContainerExtensionsTest.kt @@ -80,7 +80,7 @@ class TaskContainerExtensionsTest { val taskProvider = mock>() val tasks = mock { - on { register(eq("name"), any>()) } doReturn taskProvider + on { register(eq("clean"), any>()) } doReturn taskProvider } tasks { diff --git a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/PluginAccessorsClassPathTest.kt b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/PluginAccessorsClassPathTest.kt index 2620fa46402f7..5ae08e05e8908 100644 --- a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/PluginAccessorsClassPathTest.kt +++ b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/PluginAccessorsClassPathTest.kt @@ -22,10 +22,11 @@ import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.verifyNoMoreInteractions -import org.gradle.kotlin.dsl.concurrent.IO +import org.gradle.kotlin.dsl.concurrent.withSynchronousIO import org.gradle.kotlin.dsl.fixtures.classLoaderFor import org.gradle.kotlin.dsl.fixtures.containsMultiLineString +import org.gradle.kotlin.dsl.fixtures.pluginDescriptorEntryFor import org.gradle.kotlin.dsl.support.normaliseLineSeparators import org.gradle.kotlin.dsl.support.useToRun @@ -42,6 +43,8 @@ import org.hamcrest.MatcherAssert.assertThat import org.junit.Test +import java.io.File + class PluginAccessorsClassPathTest : TestWithClassPath() { @@ -50,6 +53,7 @@ class PluginAccessorsClassPathTest : TestWithClassPath() { // given: val pluginsJar = jarWithPluginDescriptors( + file("plugins.jar"), "my-plugin" to "MyPlugin", "my.own.plugin" to "my.own.Plugin" ) @@ -148,22 +152,10 @@ class PluginAccessorsClassPathTest : TestWithClassPath() { } private - fun jarWithPluginDescriptors(vararg pluginIdsToImplClasses: Pair) = - file("plugins.jar").also { + fun jarWithPluginDescriptors(file: File, vararg pluginIdsToImplClasses: Pair) = + file.also { zipTo(it, pluginIdsToImplClasses.asSequence().map { (id, implClass) -> - "META-INF/gradle-plugins/$id.properties" to "implementation-class=$implClass".toByteArray() + pluginDescriptorEntryFor(id, implClass) }) } } - - -internal -inline fun withSynchronousIO(action: IO.() -> Unit) { - action(SynchronousIO) -} - - -internal -object SynchronousIO : IO { - override fun io(action: () -> Unit) = action() -} diff --git a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/ProjectAccessorsClassPathTest.kt b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/ProjectAccessorsClassPathTest.kt index fb9fe04365477..0d4087ec03e41 100644 --- a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/ProjectAccessorsClassPathTest.kt +++ b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/ProjectAccessorsClassPathTest.kt @@ -49,6 +49,7 @@ import org.gradle.api.tasks.TaskProvider import org.gradle.internal.classpath.ClassPath import org.gradle.internal.classpath.DefaultClassPath +import org.gradle.kotlin.dsl.concurrent.withSynchronousIO import org.gradle.kotlin.dsl.fixtures.AbstractDslTest import org.gradle.kotlin.dsl.fixtures.eval import org.gradle.kotlin.dsl.fixtures.testRuntimeClassPath @@ -80,14 +81,10 @@ class ProjectAccessorsClassPathTest : AbstractDslTest() { entry Unit>("function1"), entry Boolean>("function2") ), - containerElements = listOf( - ), - conventions = listOf( - ), - tasks = listOf( - ), - configurations = listOf( - ) + containerElements = listOf(), + conventions = listOf(), + tasks = listOf(), + configurations = listOf() ) val function0 = mock<() -> Unit>() @@ -160,8 +157,7 @@ class ProjectAccessorsClassPathTest : AbstractDslTest() { tasks = listOf( ProjectSchemaEntry(SchemaType.of(), "task", schemaTypeFor("CustomTask")) ), - configurations = listOf( - ) + configurations = listOf() ) val srcDir = newFolder("src") diff --git a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/TestWithClassPath.kt b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/TestWithClassPath.kt index 27c3aa6675244..bfc31271b647a 100644 --- a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/TestWithClassPath.kt +++ b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/accessors/TestWithClassPath.kt @@ -117,7 +117,7 @@ fun writeClassFileTo(rootDir: File, className: InternalName, classBytes: ByteArr } -private +internal fun classBytesOf(modifiers: Int, internalName: InternalName): ByteArray = beginClass(modifiers, internalName).run { publicDefaultConstructor() diff --git a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/codegen/GradleApiExtensionsTest.kt b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/codegen/GradleApiExtensionsTest.kt index c3a86d9d4a908..be2de1aeb3b28 100644 --- a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/codegen/GradleApiExtensionsTest.kt +++ b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/codegen/GradleApiExtensionsTest.kt @@ -65,7 +65,7 @@ class GradleApiExtensionsTest : TestWithClassPath() { ClassAndGroovyNamedArguments::class ) { - assertGeneratedJarHash("54c81ce29b4ed6af36b8309be52f3f0f") + assertGeneratedJarHash("d15166f9f64f94a9bc0ef6cf4d3836af") } } diff --git a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/execution/ResidualProgramCompilerTest.kt b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/execution/ResidualProgramCompilerTest.kt index 36ec2b795bbf8..b38b04d0b9438 100644 --- a/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/execution/ResidualProgramCompilerTest.kt +++ b/subprojects/kotlin-dsl/src/test/kotlin/org/gradle/kotlin/dsl/execution/ResidualProgramCompilerTest.kt @@ -350,7 +350,7 @@ class ResidualProgramCompilerTest : TestWithTempFiles() { val fragment = source.fragment(0..6, 8..29) val stage1 = Program.Plugins(fragment) - val stage2 = source.map { it.erase(listOf(fragment.section.wholeRange)) } + val stage2 = source.map { it.erase(listOf(fragment.range)) } val stagedProgram = Dynamic( Static(ApplyPluginRequestsOf(stage1), ApplyBasePlugins), stage2 @@ -437,7 +437,7 @@ class ResidualProgramCompilerTest : TestWithTempFiles() { private fun ProgramText.without(buildscript: Program.Buildscript, plugins: Program.Plugins) = - erase(listOf(buildscript.fragment, plugins.fragment).map { it.section.wholeRange }) + erase(listOf(buildscript.fragment, plugins.fragment).map { it.range }) private fun assertStagedTopLevelProjectProgram( diff --git a/subprojects/language-groovy/src/main/java/org/gradle/api/tasks/compile/GroovyCompile.java b/subprojects/language-groovy/src/main/java/org/gradle/api/tasks/compile/GroovyCompile.java index 07af95ab1a456..993a24f2b0200 100644 --- a/subprojects/language-groovy/src/main/java/org/gradle/api/tasks/compile/GroovyCompile.java +++ b/subprojects/language-groovy/src/main/java/org/gradle/api/tasks/compile/GroovyCompile.java @@ -148,7 +148,7 @@ protected JavaToolChain getJavaToolChain() { * {@inheritDoc} */ @Override - @PathSensitive(PathSensitivity.NAME_ONLY) // Java source files are supported, too. Therefore we should care about the names. + @PathSensitive(PathSensitivity.RELATIVE) // Java source files are supported, too. Therefore we should care about the relative path. public FileTree getSource() { return super.getSource(); } diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/AbstractIncrementalAnnotationProcessingIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/AbstractIncrementalAnnotationProcessingIntegrationTest.groovy index edf80fb1db295..840868e52644f 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/AbstractIncrementalAnnotationProcessingIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/AbstractIncrementalAnnotationProcessingIntegrationTest.groovy @@ -149,5 +149,4 @@ abstract class AbstractIncrementalAnnotationProcessingIntegrationTest extends Ab succeeds 'compileJava' outputs.recompiledClasses("A") } - } diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/AggregatingIncrementalAnnotationProcessingIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/AggregatingIncrementalAnnotationProcessingIntegrationTest.groovy index b2bfa6f6cdcb1..c849215f0492b 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/AggregatingIncrementalAnnotationProcessingIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/AggregatingIncrementalAnnotationProcessingIntegrationTest.groovy @@ -19,16 +19,24 @@ package org.gradle.api.tasks.compile import org.gradle.api.internal.tasks.compile.CompileJavaBuildOperationType import org.gradle.api.internal.tasks.compile.incremental.processing.IncrementalAnnotationProcessorType import org.gradle.language.fixtures.HelperProcessorFixture -import org.gradle.language.fixtures.NonIncrementalProcessorFixture +import org.gradle.language.fixtures.ResourceGeneratingProcessorFixture import org.gradle.language.fixtures.ServiceRegistryProcessorFixture +import javax.tools.StandardLocation + import static org.gradle.api.internal.tasks.compile.CompileJavaBuildOperationType.Result.AnnotationProcessorDetails.Type.AGGREGATING class AggregatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIncrementalAnnotationProcessingIntegrationTest { + private static ServiceRegistryProcessorFixture writingResourcesTo(String location) { + def serviceRegistryProcessor = new ServiceRegistryProcessorFixture() + serviceRegistryProcessor.writeResources = true + serviceRegistryProcessor.resourceLocation = location + return serviceRegistryProcessor + } @Override def setup() { - withProcessor(new ServiceRegistryProcessorFixture()) + withProcessor(writingResourcesTo(StandardLocation.CLASS_OUTPUT.toString())) } def "generated files are recompiled when any annotated file changes"() { @@ -43,7 +51,7 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract run "compileJava" then: - outputs.recompiledClasses("A", "ServiceRegistry") + outputs.recompiledFiles("A", "ServiceRegistry", "ServiceRegistryResource.txt") serviceRegistryReferences("A", "B") } @@ -58,7 +66,7 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract run "compileJava" then: - outputs.recompiledClasses("A", "ServiceRegistry") + outputs.recompiledFiles("A", "ServiceRegistry", "ServiceRegistryResource.txt") } def "annotated files are reprocessed when an unrelated file changes"() { @@ -72,7 +80,7 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract run "compileJava" then: - outputs.recompiledClasses("Unrelated", "ServiceRegistry") + outputs.recompiledFiles("Unrelated", "ServiceRegistry", "ServiceRegistryResource.txt") } def "annotated files are reprocessed when a new file is added"() { @@ -86,7 +94,7 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract run "compileJava" then: - outputs.recompiledClasses("ServiceRegistry", "B") + outputs.recompiledFiles("ServiceRegistry", "ServiceRegistryResource.txt", "B") serviceRegistryReferences("A", "B") } @@ -103,7 +111,7 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract then: outputs.deletedClasses("A") - outputs.recompiledClasses("ServiceRegistry") + outputs.recompiledFiles("ServiceRegistry", "ServiceRegistryResource.txt") serviceRegistryReferences("B") !serviceRegistryReferences("A") } @@ -123,7 +131,7 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract run "compileJava" then: - outputs.recompiledClasses("A", "ServiceRegistry", "Dependent") + outputs.recompiledFiles("A", "ServiceRegistry", "ServiceRegistryResource.txt", "Dependent") } def "classes files of generated sources are deleted when annotated file is deleted"() { @@ -138,11 +146,12 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract run "compileJava" then: - outputs.deletedClasses("A", "ServiceRegistry") + outputs.deletedFiles("A", "ServiceRegistry", "ServiceRegistryResource.txt") } def "generated files are deleted when annotated file is deleted"() { given: + withProcessor(writingResourcesTo(StandardLocation.SOURCE_OUTPUT.toString())) def a = java "@Service class A {}" java "class Unrelated {}" @@ -151,6 +160,7 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract then: file("build/generated/sources/annotationProcessor/java/main/ServiceRegistry.java").exists() + file("build/generated/sources/annotationProcessor/java/main/ServiceRegistryResource.txt").exists() when: a.delete() @@ -158,17 +168,20 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract then: !file("build/generated/sources/annotationProcessor/java/main/ServiceRegistry.java").exists() + !file("build/generated/sources/annotationProcessor/java/main/ServiceRegistryResource.txt").exists() } def "generated files and classes are deleted when processor is removed"() { given: - def a = java "@Service class A {}" + withProcessor(writingResourcesTo(StandardLocation.SOURCE_OUTPUT.toString())) + java "@Service class A {}" when: outputs.snapshot { run "compileJava" } then: file("build/generated/sources/annotationProcessor/java/main/ServiceRegistry.java").exists() + file("build/generated/sources/annotationProcessor/java/main/ServiceRegistryResource.txt").exists() when: buildFile << "compileJava.options.annotationProcessorPath = files()" @@ -176,25 +189,40 @@ class AggregatingIncrementalAnnotationProcessingIntegrationTest extends Abstract then: !file("build/generated/sources/annotationProcessor/java/main/ServiceRegistry.java").exists() + !file("build/generated/sources/annotationProcessor/java/main/ServiceRegistryResource.txt").exists() and: outputs.deletedClasses("ServiceRegistry") } - def "writing resources triggers a full recompilation"() { + def "processors can generate identical resources in different locations"() { given: - withProcessor(new NonIncrementalProcessorFixture().writingResources().withDeclaredType(IncrementalAnnotationProcessorType.AGGREGATING)) + // Have to configure a native header output directory otherwise there will be errors; javac NPEs when files are created in NATIVE_HEADER_OUTPUT without any location set. + buildFile << ''' +compileJava.options.headerOutputDirectory = file("build/headers/java/main") +''' + def locations = [StandardLocation.SOURCE_OUTPUT.toString(), StandardLocation.NATIVE_HEADER_OUTPUT.toString(), StandardLocation.CLASS_OUTPUT.toString()] + withProcessor(new ResourceGeneratingProcessorFixture().withOutputLocations(locations).withDeclaredType(IncrementalAnnotationProcessorType.AGGREGATING)) def a = java "@Thing class A {}" - outputs.snapshot { succeeds "compileJava" } + java "class Unrelated {}" when: - a.text = "@Thing class A { void foo() {} }" + outputs.snapshot { succeeds "compileJava" } then: - succeeds "compileJava", "--info" + file("build/generated/sources/annotationProcessor/java/main/A.txt").exists() + file("build/headers/java/main/A.txt").exists() + file("build/classes/java/main/A.txt").exists() - and: - outputContains("Full recompilation is required because an annotation processor generated a resource.") + when: + a.delete() + succeeds "compileJava" + + then: "they all get cleaned" + outputs.deletedClasses("A") + !file("build/generated/sources/annotationProcessor/java/main/A.txt").exists() + !file("build/headers/java/main/A.txt").exists() + !file("build/classes/java/main/A.txt").exists() } def "an isolating processor is also a valid aggregating processor"() { diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/IsolatingIncrementalAnnotationProcessingIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/IsolatingIncrementalAnnotationProcessingIntegrationTest.groovy index 04abceb3f4514..633fcb6aafa76 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/IsolatingIncrementalAnnotationProcessingIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/api/tasks/compile/IsolatingIncrementalAnnotationProcessingIntegrationTest.groovy @@ -23,18 +23,28 @@ import org.gradle.integtests.fixtures.AvailableJavaHomes import org.gradle.language.fixtures.AnnotationProcessorFixture import org.gradle.language.fixtures.HelperProcessorFixture import org.gradle.language.fixtures.NonIncrementalProcessorFixture +import org.gradle.language.fixtures.ResourceGeneratingProcessorFixture import org.gradle.language.fixtures.ServiceRegistryProcessorFixture import org.gradle.util.TextUtil import spock.lang.Issue +import javax.tools.StandardLocation + import static org.gradle.api.internal.tasks.compile.CompileJavaBuildOperationType.Result.AnnotationProcessorDetails.Type.ISOLATING class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIncrementalAnnotationProcessingIntegrationTest { + private static HelperProcessorFixture writingResourcesTo(String location) { + def helperProcessorFixture = new HelperProcessorFixture() + helperProcessorFixture.writeResources = true + helperProcessorFixture.resourceLocation = location + return helperProcessorFixture + } + private HelperProcessorFixture helperProcessor @Override def setup() { - helperProcessor = new HelperProcessorFixture() + helperProcessor = writingResourcesTo(StandardLocation.CLASS_OUTPUT.toString()) withProcessor(helperProcessor) } @@ -50,7 +60,7 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn run "compileJava" then: - outputs.recompiledClasses("A", "AHelper") + outputs.recompiledFiles("A", "AHelper", "AHelperResource.txt") } def "annotated files are not recompiled on unrelated changes"() { @@ -83,7 +93,7 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn run "compileJava" then: - outputs.recompiledClasses("A", "AHelper", "Dependent") + outputs.recompiledFiles("A", "AHelper", "Dependent", "AHelperResource.txt") } def "source file is recompiled when dependency of generated file changes"() { @@ -114,11 +124,12 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn run "compileJava" then: - outputs.deletedClasses("A", "AHelper") + outputs.deletedFiles("A", "AHelper", "AHelperResource.txt") } def "generated files are deleted when annotated file is deleted"() { given: + withProcessor(writingResourcesTo(StandardLocation.SOURCE_OUTPUT.toString())) def a = java "@Helper class A {}" java "class Unrelated {}" @@ -127,6 +138,7 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn then: file("build/generated/sources/annotationProcessor/java/main/AHelper.java").exists() + file("build/generated/sources/annotationProcessor/java/main/AHelperResource.txt").exists() when: a.delete() @@ -134,17 +146,20 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn then: !file("build/generated/sources/annotationProcessor/java/main/AHelper.java").exists() + !file("build/generated/sources/annotationProcessor/java/main/AHelperResource.txt").exists() } def "generated files and classes are deleted when processor is removed"() { given: - def a = java "@Helper class A {}" + withProcessor(writingResourcesTo(StandardLocation.SOURCE_OUTPUT.toString())) + java "@Helper class A {}" when: outputs.snapshot { run "compileJava" } then: file("build/generated/sources/annotationProcessor/java/main/AHelper.java").exists() + file("build/generated/sources/annotationProcessor/java/main/AHelperResource.txt").exists() when: buildFile << "compileJava.options.annotationProcessorPath = files()" @@ -152,14 +167,15 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn then: !file("build/generated/sources/annotationProcessor/java/main/AHelper.java").exists() + !file("build/generated/sources/annotationProcessor/java/main/AHelperResource.txt").exists() and: - outputs.deletedClasses("AHelper") + outputs.deletedFiles("AHelper") } def "all files are recompiled when processor changes"() { given: - def a = java "@Helper class A {}" + java "@Helper class A {}" outputs.snapshot { run "compileJava" } when: @@ -168,7 +184,7 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn run "compileJava" then: - outputs.recompiledClasses("A", "AHelper") + outputs.recompiledFiles("A", "AHelper", "AHelperResource.txt") } def "all files are recompiled if compiler does not support incremental annotation processing"() { @@ -189,7 +205,7 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn run "compileJava", "--info" then: - outputs.recompiledClasses("A", "AHelper", "Unrelated") + outputs.recompiledFiles("A", "AHelper", "Unrelated", "AHelperResource.txt") and: outputContains("the chosen compiler did not support incremental annotation processing") @@ -207,7 +223,7 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn run "compileJava" then: - outputs.recompiledClasses('A', "AHelper", "Unrelated") + outputs.recompiledFiles('A', "AHelper", "AHelperResource.txt", "Unrelated") } def "all files are recompiled if a generated class is deleted"() { @@ -222,7 +238,22 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn run "compileJava" then: - outputs.recompiledClasses('A', "AHelper", "Unrelated") + outputs.recompiledFiles('A', "AHelper", "AHelperResource.txt", "Unrelated") + } + + def "all files are recompiled if a generated resource is deleted"() { + given: + java "@Helper class A {}" + java "class Unrelated {}" + + outputs.snapshot { run "compileJava" } + + when: + file("build/classes/java/main/AHelperResource.txt").delete() + run "compileJava" + + then: + outputs.recompiledFiles('A', "AHelper", "AHelperResource.txt", "Unrelated") } def "processors must provide an originating element for each source element"() { @@ -241,9 +272,9 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn outputContains("Full recompilation is required because the generated type 'AThing' must have exactly one originating element, but had 0.") } - def "writing resources triggers a full recompilation"() { + def "processors must provide an originating element for each resource"() { given: - withProcessor(new NonIncrementalProcessorFixture().writingResources().withDeclaredType(IncrementalAnnotationProcessorType.ISOLATING)) + withProcessor(new ResourceGeneratingProcessorFixture().providingNoOriginatingElements().withDeclaredType(IncrementalAnnotationProcessorType.ISOLATING)) def a = java "@Thing class A {}" outputs.snapshot { succeeds "compileJava" } @@ -254,10 +285,10 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn succeeds "compileJava", "--info" and: - outputContains("Full recompilation is required because an annotation processor generated a resource.") + outputContains("Full recompilation is required because the generated resource 'A.txt in SOURCE_OUTPUT' must have exactly one originating element, but had 0.") } - def "processors cannot provide multiple originating elements"() { + def "processors cannot provide multiple originating elements for types"() { given: withProcessor(new ServiceRegistryProcessorFixture().withDeclaredType(IncrementalAnnotationProcessorType.ISOLATING)) def a = java "@Service class A {}" @@ -275,6 +306,56 @@ class IsolatingIncrementalAnnotationProcessingIntegrationTest extends AbstractIn outputContains("Full recompilation is required because the generated type 'ServiceRegistry' must have exactly one originating element, but had 2.") } + def "processors cannot provide multiple originating elements for resources"() { + given: + def proc = new ServiceRegistryProcessorFixture() + proc.writeResources = true + withProcessor(proc.withDeclaredType(IncrementalAnnotationProcessorType.ISOLATING)) + def a = java "@Service class A {}" + java "@Service class B {}" + + outputs.snapshot { succeeds "compileJava" } + + when: + a.text = "@Service class A { void foo() {} }" + + then: + succeeds "compileJava", "--info" + + and: + outputContains("Full recompilation is required because the generated resource 'ServiceRegistryResource.txt in CLASS_OUTPUT' must have exactly one originating element, but had 2.") + } + + def "processors can generate identical resources in different locations"() { + given: + // Have to configure a native header output directory otherwise there will be errors; javac NPEs when files are created in NATIVE_HEADER_OUTPUT without any location set. + buildFile << ''' +compileJava.options.headerOutputDirectory = file("build/headers/java/main") +''' + def locations = [StandardLocation.SOURCE_OUTPUT.toString(), StandardLocation.NATIVE_HEADER_OUTPUT.toString(), StandardLocation.CLASS_OUTPUT.toString()] + withProcessor(new ResourceGeneratingProcessorFixture().withOutputLocations(locations).withDeclaredType(IncrementalAnnotationProcessorType.ISOLATING)) + def a = java "@Thing class A {}" + java "class Unrelated {}" + + when: + outputs.snapshot { succeeds "compileJava" } + + then: + file("build/generated/sources/annotationProcessor/java/main/A.txt").exists() + file("build/headers/java/main/A.txt").exists() + file("build/classes/java/main/A.txt").exists() + + when: + a.delete() + succeeds "compileJava" + + then: "they all get cleaned" + outputs.deletedClasses("A") + !file("build/generated/sources/annotationProcessor/java/main/A.txt").exists() + !file("build/headers/java/main/A.txt").exists() + !file("build/classes/java/main/A.txt").exists() + } + @Issue(["https://github.com/gradle/gradle/issues/8128", "https://bugs.openjdk.java.net/browse/JDK-8162455"]) def "incremental processing doesn't trigger unmatched processor option warning"() { buildFile << """ diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/AbstractCrossTaskIncrementalJavaCompilationIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/AbstractCrossTaskIncrementalJavaCompilationIntegrationTest.groovy index acc49f14e9412..dd77f79a7156c 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/AbstractCrossTaskIncrementalJavaCompilationIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/AbstractCrossTaskIncrementalJavaCompilationIntegrationTest.groovy @@ -44,6 +44,8 @@ abstract class AbstractCrossTaskIncrementalJavaCompilationIntegrationTest extend protected abstract String getProjectDependencyBlock() + protected abstract void addDependency(String from, String to) + private File java(Map projectToClassBodies) { File out projectToClassBodies.each { project, bodies -> @@ -71,17 +73,66 @@ abstract class AbstractCrossTaskIncrementalJavaCompilationIntegrationTest extend impl.recompiledClasses("ImplA") } + def "detects change to transitive superclass in an upstream project"() { + settingsFile << """ + include 'app' + """ + addDependency("app", "impl") + def app = new CompilationOutputsFixture(file("app/build/classes")) + java api: ["class A {}"] + java impl: ["class B extends A {}"] + java app: ["class Unrelated {}", "class C extends B {}", "class D extends C {}"] + app.snapshot { run "compileJava" } + + when: + java api: ["class A { String change; }"] + run "app:compileJava" + + then: + app.recompiledClasses("C", "D") + } + def "detects change to transitive dependency in an upstream project"() { - java api: ["class A {}", "class B extends A {}"] - java impl: ["class SomeImpl {}", "class ImplB extends B {}", "class ImplB2 extends ImplB {}"] - impl.snapshot { run "compileJava" } + settingsFile << """ + include 'app' + """ + addDependency("app", "impl") + def app = new CompilationOutputsFixture(file("app/build/classes")) + java api: ["class A {}"] + java impl: ["class B { public A a;}"] + java app: ["class Unrelated {}", "class C { public B b; }"] + app.snapshot { run "compileJava" } when: java api: ["class A { String change; }"] - run "impl:compileJava" + run "app:compileJava" then: - impl.recompiledClasses("ImplB", "ImplB2") + app.recompiledClasses("C") + } + + def "detects deletions of transitive dependency in an upstream project"() { + settingsFile << """ + include 'app' + """ + addDependency("app", "impl") + def app = new CompilationOutputsFixture(file("app/build/classes")) + java api: ["class A {}"] + java impl: ["class B { public A a;}"] + java app: ["class Unrelated {}", "class C { public B b; }"] + app.snapshot { + impl.snapshot { + run "compileJava" + } + } + + when: + file("api/src/main/java/A.java").delete() + run "app:compileJava", "-x", "impl:compileJava" + + then: + impl.noneRecompiled() + app.recompiledClasses("C") } def "deletion of jar without dependents does not recompile any classes"() { diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationIntegrationTest.groovy index 799a81208e737..6b54347d22da1 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationIntegrationTest.groovy @@ -27,4 +27,13 @@ class CrossTaskIncrementalJavaCompilationIntegrationTest extends AbstractCrossTa } ''' } + + @Override + protected void addDependency(String from, String to) { + buildFile << """ + project(':$from') { + dependencies { compile project(':$to') } + } + """ + } } diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationUsingClassDirectoryIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationUsingClassDirectoryIntegrationTest.groovy index 6c82b29058d74..f6ca1d0264c0f 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationUsingClassDirectoryIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/java/compile/incremental/CrossTaskIncrementalJavaCompilationUsingClassDirectoryIntegrationTest.groovy @@ -22,9 +22,11 @@ class CrossTaskIncrementalJavaCompilationUsingClassDirectoryIntegrationTest exte @Override protected String getProjectDependencyBlock() { ''' - project(':api') { + subprojects { configurations { - classesDir + classesDir { + extendsFrom(compile) + } } artifacts { classesDir file: compileJava.destinationDir, builtBy:compileJava @@ -35,4 +37,13 @@ class CrossTaskIncrementalJavaCompilationUsingClassDirectoryIntegrationTest exte } ''' } + + @Override + protected void addDependency(String from, String to) { + buildFile << """ + project(':$from') { + dependencies { compile project(path:':$to', configuration: 'classesDir') } + } + """ + } } diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageCustomLibraryDependencyResolutionIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageCustomLibraryDependencyResolutionIntegrationTest.groovy index e48b5f37f249a..ddd94965e7067 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageCustomLibraryDependencyResolutionIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageCustomLibraryDependencyResolutionIntegrationTest.groovy @@ -142,15 +142,15 @@ model { model { components { zdep(CustomLibrary) { - javaVersions 6,7 + javaVersions 7,8 sources { java(JavaSourceSet) { } } } main(JvmLibrarySpec) { - targetPlatform 'java6' targetPlatform 'java7' + targetPlatform 'java8' sources { java { dependencies { @@ -162,18 +162,18 @@ model { } tasks { - mainJava6Jar { - doLast { - assert compileMainJava6JarMainJava.taskDependencies.getDependencies(compileMainJava6JarMainJava).contains(zdep6ApiJar) - assert compileMainJava6JarMainJava.classpath.files == [file("${buildDir}/jars/zdep/6Jar/api/zdep.jar")] as Set - } - } mainJava7Jar { doLast { assert compileMainJava7JarMainJava.taskDependencies.getDependencies(compileMainJava7JarMainJava).contains(zdep7ApiJar) assert compileMainJava7JarMainJava.classpath.files == [file("${buildDir}/jars/zdep/7Jar/api/zdep.jar")] as Set } } + mainJava8Jar { + doLast { + assert compileMainJava8JarMainJava.taskDependencies.getDependencies(compileMainJava8JarMainJava).contains(zdep8ApiJar) + assert compileMainJava8JarMainJava.classpath.files == [file("${buildDir}/jars/zdep/8Jar/api/zdep.jar")] as Set + } + } } } ''' @@ -181,16 +181,16 @@ model { file('src/main/java/TestApp.java') << 'public class TestApp {}' when: - succeeds ':mainJava6Jar' + succeeds ':mainJava7Jar' then: - executedAndNotSkipped ':zdep6ApiJar', ':mainJava6Jar' + executedAndNotSkipped ':zdep7ApiJar', ':mainJava7Jar' when: - succeeds ':mainJava7Jar' + succeeds ':mainJava8Jar' then: - executedAndNotSkipped ':zdep7ApiJar', ':mainJava7Jar' + executedAndNotSkipped ':zdep8ApiJar', ':mainJava8Jar' } def "should fail resolving dependencies only for the missing dependency variant"() { @@ -610,14 +610,14 @@ model { succeeds ':thirdJar' } - def "All components should depend on the corresponding variants"() { + def "all components should depend on the corresponding variants"() { given: theModel ''' model { components { main(JvmLibrarySpec) { - targetPlatform 'java6' targetPlatform 'java7' + targetPlatform 'java8' sources { java { dependencies { @@ -627,7 +627,7 @@ model { } } second(CustomLibrary) { - javaVersions 6,7 + javaVersions 7,8 sources { java(JavaSourceSet) { dependencies { @@ -637,28 +637,22 @@ model { } } third(JvmLibrarySpec) { - targetPlatform 'java6' targetPlatform 'java7' + targetPlatform 'java8' } } tasks { - mainJava6Jar { - doLast { - assert compileMainJava6JarMainJava.taskDependencies.getDependencies(compileMainJava6JarMainJava).contains(second6ApiJar) - assert compileMainJava6JarMainJava.classpath.files == [file("${buildDir}/jars/second/6Jar/api/second.jar")] as Set - } - } mainJava7Jar { doLast { assert compileMainJava7JarMainJava.taskDependencies.getDependencies(compileMainJava7JarMainJava).contains(second7ApiJar) assert compileMainJava7JarMainJava.classpath.files == [file("${buildDir}/jars/second/7Jar/api/second.jar")] as Set } } - second6Jar { + mainJava8Jar { doLast { - assert compileSecond6JarSecondJava.taskDependencies.getDependencies(compileSecond6JarSecondJava).contains(thirdJava6ApiJar) - assert compileSecond6JarSecondJava.classpath.files == [file("${buildDir}/jars/third/java6Jar/api/third.jar")] as Set + assert compileMainJava8JarMainJava.taskDependencies.getDependencies(compileMainJava8JarMainJava).contains(second8ApiJar) + assert compileMainJava8JarMainJava.classpath.files == [file("${buildDir}/jars/second/8Jar/api/second.jar")] as Set } } second7Jar { @@ -667,6 +661,12 @@ model { assert compileSecond7JarSecondJava.classpath.files == [file("${buildDir}/jars/third/java7Jar/api/third.jar")] as Set } } + second8Jar { + doLast { + assert compileSecond8JarSecondJava.taskDependencies.getDependencies(compileSecond8JarSecondJava).contains(thirdJava8ApiJar) + assert compileSecond8JarSecondJava.classpath.files == [file("${buildDir}/jars/third/java8Jar/api/third.jar")] as Set + } + } } } ''' @@ -680,15 +680,15 @@ model { then: executedAndNotSkipped ':tasks' + and: "Can build the Java 8 variant of all components" + succeeds ':mainJava8Jar' + succeeds ':second8Jar' + succeeds ':thirdJava8Jar' + and: "Can build the Java 7 variant of all components" succeeds ':mainJava7Jar' succeeds ':second7Jar' succeeds ':thirdJava7Jar' - - and: "Can build the Java 6 variant of all components" - succeeds ':mainJava6Jar' - succeeds ':second6Jar' - succeeds ':thirdJava6Jar' } def "can define a cyclic dependency"() { diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageDependencyResolutionIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageDependencyResolutionIntegrationTest.groovy index acdb830a917bd..d93ab010c4ea8 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageDependencyResolutionIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageDependencyResolutionIntegrationTest.groovy @@ -899,12 +899,12 @@ model { model { components { dep(JvmLibrarySpec) { - targetPlatform 'java6' + targetPlatform 'java7' } main(JvmLibrarySpec) { + targetPlatform 'java8' targetPlatform 'java7' - targetPlatform 'java6' $scope.begin library 'dep' $scope.end @@ -912,11 +912,11 @@ model { } tasks { - mainJava6Jar.finalizedBy('checkDependencies') mainJava7Jar.finalizedBy('checkDependencies') + mainJava8Jar.finalizedBy('checkDependencies') create('checkDependencies') { - assert compileMainJava6JarMainJava.taskDependencies.getDependencies(compileMainJava6JarMainJava).contains(depApiJar) assert compileMainJava7JarMainJava.taskDependencies.getDependencies(compileMainJava7JarMainJava).contains(depApiJar) + assert compileMainJava8JarMainJava.taskDependencies.getDependencies(compileMainJava8JarMainJava).contains(depApiJar) } } } @@ -931,10 +931,10 @@ model { executedAndNotSkipped ':tasks' and: - succeeds 'mainJava6Jar' + succeeds 'mainJava7Jar' and: - succeeds 'mainJava7Jar' + succeeds 'mainJava8Jar' where: scope << DependencyScope.values() @@ -1088,13 +1088,13 @@ model { model { components { dep(JvmLibrarySpec) { - targetPlatform 'java6' targetPlatform 'java7' + targetPlatform 'java8' } main(JvmLibrarySpec) { targetPlatform 'java7' - targetPlatform 'java6' + targetPlatform 'java8' $scope.begin library 'dep' $scope.end @@ -1102,11 +1102,11 @@ model { } tasks { - mainJava6Jar.finalizedBy('checkDependencies') mainJava7Jar.finalizedBy('checkDependencies') + mainJava8Jar.finalizedBy('checkDependencies') create('checkDependencies') { - assert compileMainJava6JarMainJava.taskDependencies.getDependencies(compileMainJava6JarMainJava).contains(depJava6ApiJar) assert compileMainJava7JarMainJava.taskDependencies.getDependencies(compileMainJava7JarMainJava).contains(depJava7ApiJar) + assert compileMainJava8JarMainJava.taskDependencies.getDependencies(compileMainJava8JarMainJava).contains(depJava8ApiJar) } } } @@ -1121,13 +1121,13 @@ model { executedAndNotSkipped ':tasks' and: - succeeds 'mainJava6Jar', 'mainJava7Jar' + succeeds 'mainJava7Jar', 'mainJava8Jar' where: scope << DependencyScope.values() } - @Requires(TestPrecondition.JDK8_OR_LATER) + @Requires(TestPrecondition.JDK9_OR_LATER) def "should not choose higher version than available"() { given: applyJavaPlugin(buildFile) @@ -1135,14 +1135,14 @@ model { model { components { dep(JvmLibrarySpec) { - targetPlatform 'java6' targetPlatform 'java7' targetPlatform 'java8' + targetPlatform 'java9' } main(JvmLibrarySpec) { targetPlatform 'java7' - targetPlatform 'java6' + targetPlatform 'java8' sources { java { dependencies { @@ -1154,11 +1154,11 @@ model { } tasks { - mainJava6Jar.finalizedBy('checkDependencies') mainJava7Jar.finalizedBy('checkDependencies') + mainJava8Jar.finalizedBy('checkDependencies') create('checkDependencies') { - assert compileMainJava6JarMainJava.taskDependencies.getDependencies(compileMainJava6JarMainJava).contains(depJava6ApiJar) assert compileMainJava7JarMainJava.taskDependencies.getDependencies(compileMainJava7JarMainJava).contains(depJava7ApiJar) + assert compileMainJava8JarMainJava.taskDependencies.getDependencies(compileMainJava8JarMainJava).contains(depJava8ApiJar) } } } @@ -1173,10 +1173,10 @@ model { executedAndNotSkipped ':tasks' then: - succeeds 'mainJava6Jar' + succeeds 'mainJava7Jar' and: - succeeds 'mainJava7Jar' + succeeds 'mainJava8Jar' } def "should display candidate platforms if no one matches"() { diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageIntegrationTest.groovy index 81ca28cb15d7e..73509363fdc13 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaLanguageIntegrationTest.groovy @@ -59,7 +59,7 @@ class JavaLanguageIntegrationTest extends AbstractJvmLanguageIntegrationTest { model { components { myLib(JvmLibrarySpec) { - targetPlatform "java6" + targetPlatform "java7" } } } @@ -68,11 +68,11 @@ class JavaLanguageIntegrationTest extends AbstractJvmLanguageIntegrationTest { succeeds "assemble" and: - jarFile("build/jars/myLib/jar/myLib.jar").javaVersion == JavaVersion.VERSION_1_6 + jarFile("build/jars/myLib/jar/myLib.jar").javaVersion == JavaVersion.VERSION_1_7 jarFile("build/jars/myLib/jar/myLib.jar").hasDescendants(app.sources*.classFile.fullPath as String[]) } - @Requires(TestPrecondition.JDK8_OR_LATER) + @Requires(TestPrecondition.JDK9_OR_LATER) def "multiple targets should produce in the correct bytecode"() { when: app.writeSources(file("src/myLib")) @@ -82,9 +82,9 @@ class JavaLanguageIntegrationTest extends AbstractJvmLanguageIntegrationTest { model { components { myLib(JvmLibrarySpec) { - targetPlatform "java6" targetPlatform "java7" targetPlatform "java8" + targetPlatform "java9" } } } @@ -93,9 +93,9 @@ class JavaLanguageIntegrationTest extends AbstractJvmLanguageIntegrationTest { succeeds "assemble" and: - jarFile("build/jars/myLib/java6Jar/myLib.jar").javaVersion == JavaVersion.VERSION_1_6 jarFile("build/jars/myLib/java7Jar/myLib.jar").javaVersion == JavaVersion.VERSION_1_7 jarFile("build/jars/myLib/java8Jar/myLib.jar").javaVersion == JavaVersion.VERSION_1_8 + jarFile("build/jars/myLib/java9Jar/myLib.jar").javaVersion == JavaVersion.VERSION_1_9 } def "erroneous target should produce reasonable error message"() { diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaSourceSetIntegrationTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaSourceSetIntegrationTest.groovy index ed8ac4408ea13..a102eb83c1aa6 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaSourceSetIntegrationTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/JavaSourceSetIntegrationTest.groovy @@ -148,26 +148,26 @@ model { def "can build JAR from multiple source sets"() { given: file("src/main/java/Main.java") << "public class Main {}" - file("src/main/resources/main.properties") << "java=6" - file("src/main/java7/Java7.java") << "public class Java7 {}" - file("src/main/java7-resources/java7.properties") << "java=7" + file("src/main/resources/main.properties") << "java=7" + file("src/main/java8/Java8.java") << "public class Java8 {}" + file("src/main/java8-resources/java8.properties") << "java=8" applyJavaPlugin(buildFile) buildFile << ''' model { components { main(JvmLibrarySpec) { - targetPlatform 'java6' targetPlatform 'java7' + targetPlatform 'java8' binaries { withType(JarBinarySpec) { binary -> - if (binary.targetPlatform.name == "java7") { + if (binary.targetPlatform.name == "java8") { sources { - java7(JavaSourceSet) { - source.srcDir "src/main/java7" + java8(JavaSourceSet) { + source.srcDir "src/main/java8" } - java7Resources(JvmResourceSet) { - source.srcDir "src/main/java7-resources" + java8Resources(JvmResourceSet) { + source.srcDir "src/main/java8-resources" } } } @@ -182,10 +182,10 @@ model { succeeds "assemble" then: - new JarTestFixture(file("build/jars/main/java6Jar/main.jar")).hasDescendants( - "Main.class", "main.properties"); new JarTestFixture(file("build/jars/main/java7Jar/main.jar")).hasDescendants( - "Main.class", "main.properties", "Java7.class", "java7.properties"); + "Main.class", "main.properties"); + new JarTestFixture(file("build/jars/main/java8Jar/main.jar")).hasDescendants( + "Main.class", "main.properties", "Java8.class", "java8.properties"); } } diff --git a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/SingleBinaryTypeWithVariantsTest.groovy b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/SingleBinaryTypeWithVariantsTest.groovy index f7a5900032f99..387583feae36e 100644 --- a/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/SingleBinaryTypeWithVariantsTest.groovy +++ b/subprojects/language-java/src/integTest/groovy/org/gradle/language/java/SingleBinaryTypeWithVariantsTest.groovy @@ -16,6 +16,8 @@ package org.gradle.language.java +import org.gradle.api.JavaVersion +import org.junit.Assume import spock.lang.Unroll import static org.gradle.language.java.JavaIntegrationTesting.applyJavaPlugin @@ -24,6 +26,8 @@ class SingleBinaryTypeWithVariantsTest extends VariantAwareDependencyResolutionS @Unroll("matching {jdk #jdk1, flavors #flavors1, builtTypes #buildTypes1} with {jdk #jdk2, flavors #flavors2, buildTypes #buildTypes2} #outcome") def "check resolution of a custom component onto a component of the same type (same class, same variant dimensions)"() { + assertAllTargetVersionsAreSupported(jdk1) + given: applyJavaPlugin(buildFile) addCustomLibraryType(buildFile) @@ -98,39 +102,39 @@ model { where: jdk1 | buildTypes1 | flavors1 | jdk2 | buildTypes2 | flavors2 | selected | errors - [6] | [] | [] | [6] | [] | [] | [:] | [:] - [6] | [] | [] | [6] | [] | [] | [firstDefaultDefaultJar: 'second/defaultDefaultJar'] | [:] - [6] | [] | [] | [6] | [] | ['paid'] | [firstDefaultDefaultJar: 'second/paidDefaultJar'] | [:] - [6] | [] | [] | [6] | [] | ['paid', 'free'] | [:] | [firstDefaultDefaultJar: ["Multiple compatible variants found for library 'second':", - "Jar 'second:freeDefaultJar' [flavor:'free', platform:'java6']", - "Jar 'second:paidDefaultJar' [flavor:'paid', platform:'java6']"]] - [6] | ['release'] | [] | [6] | ['debug'] | [] | [:] | [firstDefaultReleaseJar: ["Cannot find a compatible variant for library 'second'.", - "Required platform 'java6', available: 'java6'", + [7] | [] | [] | [7] | [] | [] | [:] | [:] + [7] | [] | [] | [7] | [] | [] | [firstDefaultDefaultJar: 'second/defaultDefaultJar'] | [:] + [7] | [] | [] | [7] | [] | ['paid'] | [firstDefaultDefaultJar: 'second/paidDefaultJar'] | [:] + [7] | [] | [] | [7] | [] | ['paid', 'free'] | [:] | [firstDefaultDefaultJar: ["Multiple compatible variants found for library 'second':", + "Jar 'second:freeDefaultJar' [flavor:'free', platform:'java7']", + "Jar 'second:paidDefaultJar' [flavor:'paid', platform:'java7']"]] + [7] | ['release'] | [] | [7] | ['debug'] | [] | [:] | [firstDefaultReleaseJar: ["Cannot find a compatible variant for library 'second'.", + "Required platform 'java7', available: 'java7'", "Required buildType 'release', available: 'debug'"]] - [6] | [] | [] | [6] | ['release', 'debug'] | ['paid', 'free'] | [:] | [firstDefaultDefaultJar: ["Multiple compatible variants found for library 'second':", - "Jar 'second:freeDebugJar' [buildType:'debug', flavor:'free', platform:'java6']", - "Jar 'second:freeReleaseJar' [buildType:'release', flavor:'free', platform:'java6']", - "Jar 'second:paidDebugJar' [buildType:'debug', flavor:'paid', platform:'java6']", - "Jar 'second:paidReleaseJar' [buildType:'release', flavor:'paid', platform:'java6']"]] - [6] | [] | ['paid'] | [6] | [] | ['paid'] | [firstPaidDefaultJar: 'second/paidDefaultJar'] | [:] - [6] | [] | ['paid', 'free'] | [6] | [] | ['paid', 'free'] | [firstFreeDefaultJar: 'second/freeDefaultJar', + [7] | [] | [] | [7] | ['release', 'debug'] | ['paid', 'free'] | [:] | [firstDefaultDefaultJar: ["Multiple compatible variants found for library 'second':", + "Jar 'second:freeDebugJar' [buildType:'debug', flavor:'free', platform:'java7']", + "Jar 'second:freeReleaseJar' [buildType:'release', flavor:'free', platform:'java7']", + "Jar 'second:paidDebugJar' [buildType:'debug', flavor:'paid', platform:'java7']", + "Jar 'second:paidReleaseJar' [buildType:'release', flavor:'paid', platform:'java7']"]] + [7] | [] | ['paid'] | [7] | [] | ['paid'] | [firstPaidDefaultJar: 'second/paidDefaultJar'] | [:] + [7] | [] | ['paid', 'free'] | [7] | [] | ['paid', 'free'] | [firstFreeDefaultJar: 'second/freeDefaultJar', firstPaidDefaultJar: 'second/paidDefaultJar'] | [:] - [6] | ['debug'] | ['free'] | [6] | ['debug', 'release'] | ['free'] | [firstFreeDebugJar: 'second/freeDebugJar'] | [:] - [6] | ['debug'] | ['free'] | [6] | ['debug'] | ['free', 'paid'] | [firstFreeDebugJar: 'second/freeDebugJar'] | [:] - [6] | ['debug'] | ['free'] | [5, 6, 7] | ['debug'] | ['free'] | [firstFreeDebugJar: 'second/freeDebug6Jar'] | [:] - [6] | ['debug'] | ['free'] | [7, 6, 5] | ['debug'] | ['free'] | [firstFreeDebugJar: 'second/freeDebug6Jar'] | [:] - [7] | ['debug'] | ['free'] | [5, 6] | ['debug'] | ['free'] | [firstFreeDebugJar: 'second/freeDebug6Jar'] | [:] - [6] | ['debug'] | ['free'] | [7] | ['debug'] | ['free'] | [:] | [firstFreeDebugJar: ["Cannot find a compatible variant for library 'second'", - "Required platform 'java6', available: 'java7'", + [7] | ['debug'] | ['free'] | [7] | ['debug', 'release'] | ['free'] | [firstFreeDebugJar: 'second/freeDebugJar'] | [:] + [7] | ['debug'] | ['free'] | [7] | ['debug'] | ['free', 'paid'] | [firstFreeDebugJar: 'second/freeDebugJar'] | [:] + [8] | ['debug'] | ['free'] | [7, 8, 9] | ['debug'] | ['free'] | [firstFreeDebugJar: 'second/freeDebug8Jar'] | [:] + [8] | ['debug'] | ['free'] | [9, 8, 7] | ['debug'] | ['free'] | [firstFreeDebugJar: 'second/freeDebug8Jar'] | [:] + [9] | ['debug'] | ['free'] | [7, 8] | ['debug'] | ['free'] | [firstFreeDebugJar: 'second/freeDebug8Jar'] | [:] + [7] | ['debug'] | ['free'] | [8] | ['debug'] | ['free'] | [:] | [firstFreeDebugJar: ["Cannot find a compatible variant for library 'second'", + "Required platform 'java7', available: 'java8'", "Required flavor 'free', available: 'free'", "Required buildType 'debug', available: 'debug'"]] - [6] | [] | ['paid'] | [6] | [] | ['free'] | [:] | [firstPaidDefaultJar: ["Cannot find a compatible variant for library 'second'", + [7] | [] | ['paid'] | [7] | [] | ['free'] | [:] | [firstPaidDefaultJar: ["Cannot find a compatible variant for library 'second'", "Required flavor 'paid', available: 'free'"]] - [6] | [] | ['paid'] | [6] | [] | ['free', 'other'] | [:] | [firstPaidDefaultJar: ["Cannot find a compatible variant for library 'second'", + [7] | [] | ['paid'] | [7] | [] | ['free', 'other'] | [:] | [firstPaidDefaultJar: ["Cannot find a compatible variant for library 'second'", "Required flavor 'paid', available: 'free', 'other'"]] - [6] | [] | ['paid', 'free'] | [6] | [] | ['free', 'other'] | [:] | [firstPaidDefaultJar: ["Cannot find a compatible variant for library 'second'", + [7] | [] | ['paid', 'free'] | [7] | [] | ['free', 'other'] | [:] | [firstPaidDefaultJar: ["Cannot find a compatible variant for library 'second'", "Required flavor 'paid', available: 'free', 'other'"]] - [6] | [] | ['paid', 'test'] | [6] | [] | ['free', 'other'] | [:] | [firstPaidDefaultJar: ["Cannot find a compatible variant for library 'second'", + [7] | [] | ['paid', 'test'] | [7] | [] | ['free', 'other'] | [:] | [firstPaidDefaultJar: ["Cannot find a compatible variant for library 'second'", "Required flavor 'paid', available: 'free', 'other'"], firstTestDefaultJar: ["Cannot find a compatible variant for library 'second'", "Required flavor 'test', available: 'free', 'other'"]] @@ -140,6 +144,8 @@ model { @Unroll("matching custom {jdk #jdk1, flavors #flavors1, builtTypes #buildTypes1} with Java {jdk #jdk2} #outcome") def "building a custom binary type and resolving against a library with standard JarBinarySpec instances"() { + assertAllTargetVersionsAreSupported(jdk1) + given: applyJavaPlugin(buildFile) addCustomLibraryType(buildFile) @@ -199,18 +205,18 @@ model { where: jdk1 | buildTypes1 | flavors1 | jdk2 | selected | errors - [6] | [] | [] | [6] | [:] | [:] - [6] | ['debug'] | ['free'] | [6, 7] | [firstFreeDebugJar: 'second/java6Jar'] | [:] - [6, 7] | ['debug'] | ['free'] | [6, 7] | [firstFreeDebug6Jar: 'second/java6Jar', - firstFreeDebug7Jar: 'second/java7Jar'] | [:] - [5, 6] | ['debug'] | ['free'] | [6, 7] | [firstFreeDebug6Jar: 'second/java6Jar'] | [firstFreeDebug5Jar: ["Cannot find a compatible variant for library 'second'", - "Required platform 'java5', available: 'java6', 'java7'", + [7] | [] | [] | [7] | [:] | [:] + [7] | ['debug'] | ['free'] | [7, 8] | [firstFreeDebugJar: 'second/java7Jar'] | [:] + [8, 9] | ['debug'] | ['free'] | [8, 9] | [firstFreeDebug8Jar: 'second/java8Jar', + firstFreeDebug9Jar: 'second/java9Jar'] | [:] + [7, 8] | ['debug'] | ['free'] | [8, 9] | [firstFreeDebug8Jar: 'second/java8Jar'] | [firstFreeDebug7Jar: ["Cannot find a compatible variant for library 'second'", + "Required platform 'java7', available: 'java8', 'java9'", "Required flavor 'free' but no compatible variant was found", "Required buildType 'debug' but no compatible variant was found"]] - [6] | ['debug', 'release'] | ['free', 'paid'] | [6, 7] | [firstFreeDebugJar : 'second/java6Jar', - firstFreeReleaseJar: 'second/java6Jar', - firstPaidDebugJar : 'second/java6Jar', - firstPaidReleaseJar: 'second/java6Jar'] | [:] + [7] | ['debug', 'release'] | ['free', 'paid'] | [7, 8] | [firstFreeDebugJar : 'second/java7Jar', + firstFreeReleaseJar: 'second/java7Jar', + firstPaidDebugJar : 'second/java7Jar', + firstPaidReleaseJar: 'second/java7Jar'] | [:] and: outcome = errors ? 'fails' : 'succeeds' @@ -218,6 +224,8 @@ model { @Unroll("matching Java #jdk1 with custom {jdk #jdk2, flavors #flavors2, builtTypes #buildTypes2} #outcome") def "building a standard JarBinarySpec instance and resolving against a library with custom binary types. "() { + assertAllTargetVersionsAreSupported(jdk1) + given: applyJavaPlugin(buildFile) addCustomLibraryType(buildFile) @@ -277,20 +285,20 @@ model { where: jdk1 | buildTypes2 | flavors2 | jdk2 | selected | errors - [6] | [] | [] | [6] | [:] | [:] - [6] | ['debug'] | ['free'] | [6, 7] | [firstJar: 'second/freeDebug6Jar'] | [:] - [6, 7] | ['debug'] | ['free'] | [6, 7] | [firstJava6Jar: 'second/freeDebug6Jar', - firstJava7Jar: 'second/freeDebug7Jar'] | [:] - [5, 6] | ['debug'] | ['free'] | [6, 7] | [firstJava6Jar: 'second/freeDebug6Jar'] | [firstJava5Jar: ["Cannot find a compatible variant for library 'second'.", - "Required platform 'java5', available: 'java6', 'java7'"]] - [6] | ['debug', 'release'] | [] | [6, 7] | [:] | [firstJar: ["Multiple compatible variants found for library 'second':", - "Jar 'second:defaultDebug6Jar' [buildType:'debug', platform:'java6']", - "Jar 'second:defaultRelease6Jar' [buildType:'release', platform:'java6']"]] - [6] | ['debug', 'release'] | ['free', 'paid'] | [6, 7] | [:] | [firstJar: ["Multiple compatible variants found for library 'second':", - "Jar 'second:freeDebug6Jar' [buildType:'debug', flavor:'free', platform:'java6']", - "Jar 'second:freeRelease6Jar' [buildType:'release', flavor:'free', platform:'java6']", - "Jar 'second:paidDebug6Jar' [buildType:'debug', flavor:'paid', platform:'java6']", - "Jar 'second:paidRelease6Jar' [buildType:'release', flavor:'paid', platform:'java6']"]] + [7] | [] | [] | [7] | [:] | [:] + [7] | ['debug'] | ['free'] | [7, 8] | [firstJar: 'second/freeDebug7Jar'] | [:] + [8, 9] | ['debug'] | ['free'] | [8, 9] | [firstJava8Jar: 'second/freeDebug8Jar', + firstJava9Jar: 'second/freeDebug9Jar'] | [:] + [7, 8] | ['debug'] | ['free'] | [8, 9] | [firstJava8Jar: 'second/freeDebug8Jar'] | [firstJava7Jar: ["Cannot find a compatible variant for library 'second'.", + "Required platform 'java7', available: 'java8', 'java9'"]] + [7] | ['debug', 'release'] | [] | [7, 8] | [:] | [firstJar: ["Multiple compatible variants found for library 'second':", + "Jar 'second:defaultDebug7Jar' [buildType:'debug', platform:'java7']", + "Jar 'second:defaultRelease7Jar' [buildType:'release', platform:'java7']"]] + [7] | ['debug', 'release'] | ['free', 'paid'] | [7, 8] | [:] | [firstJar: ["Multiple compatible variants found for library 'second':", + "Jar 'second:freeDebug7Jar' [buildType:'debug', flavor:'free', platform:'java7']", + "Jar 'second:freeRelease7Jar' [buildType:'release', flavor:'free', platform:'java7']", + "Jar 'second:paidDebug7Jar' [buildType:'debug', flavor:'paid', platform:'java7']", + "Jar 'second:paidRelease7Jar' [buildType:'release', flavor:'paid', platform:'java7']"]] and: outcome = errors ? 'fails' : 'succeeds' @@ -298,6 +306,8 @@ model { @Unroll("matching custom1 {jdk #jdk1, flavors #flavors) with custom2 {jdk #jdk2, builtTypes #buildTypes} #outcome") def "building a custom JarBinarySpec type and resolving against a library with a different custom JarBinarySpec type"() { + assertAllTargetVersionsAreSupported(jdk1) + given: applyJavaPlugin(buildFile) addCustomLibraryType(buildFile) @@ -361,25 +371,32 @@ model { where: jdk1 | flavors | buildTypes | jdk2 | selected | errors - [6] | [] | [] | [6] | [:] | [:] - [6] | ['free'] | ['debug'] | [6, 7] | [firstFreeJar: 'second/debug6Jar'] | [:] - [6, 7] | ['free'] | ['debug'] | [6, 7] | [firstFree6Jar: 'second/debug6Jar', - firstFree7Jar: 'second/debug7Jar'] | [:] - [5, 6] | ['free'] | ['debug'] | [6, 7] | [firstFree6Jar: 'second/debug6Jar'] | [firstFree5Jar: ["Cannot find a compatible variant for library 'second'", - "Required platform 'java5', available: 'java6', 'java7'", + [7] | [] | [] | [7] | [:] | [:] + [7] | ['free'] | ['debug'] | [7, 8] | [firstFreeJar: 'second/debug7Jar'] | [:] + [8, 9] | ['free'] | ['debug'] | [8, 9] | [firstFree8Jar: 'second/debug8Jar', + firstFree9Jar: 'second/debug9Jar'] | [:] + [7, 8] | ['free'] | ['debug'] | [8, 9] | [firstFree8Jar: 'second/debug8Jar'] | [firstFree7Jar: ["Cannot find a compatible variant for library 'second'", + "Required platform 'java7', available: 'java8', 'java9'", "Required flavor 'free' but no compatible variant was found"]] - [6] | [] | ['debug', 'release'] | [6, 7] | [:] | [firstDefaultJar: ["Multiple compatible variants found for library 'second':", - "Jar 'second:debug6Jar' [buildType:'debug', platform:'java6']", - "Jar 'second:release6Jar' [buildType:'release', platform:'java6']"]] - [6] | ['free', 'paid'] | ['debug', 'release'] | [6, 7] | [:] | [firstFreeJar: ["Multiple compatible variants found for library 'second':", - "Jar 'second:debug6Jar' [buildType:'debug', platform:'java6']", - "Jar 'second:release6Jar' [buildType:'release', platform:'java6']"], + [7] | [] | ['debug', 'release'] | [7, 8] | [:] | [firstDefaultJar: ["Multiple compatible variants found for library 'second':", + "Jar 'second:debug7Jar' [buildType:'debug', platform:'java7']", + "Jar 'second:release7Jar' [buildType:'release', platform:'java7']"]] + [7] | ['free', 'paid'] | ['debug', 'release'] | [7, 8] | [:] | [firstFreeJar: ["Multiple compatible variants found for library 'second':", + "Jar 'second:debug7Jar' [buildType:'debug', platform:'java7']", + "Jar 'second:release7Jar' [buildType:'release', platform:'java7']"], firstPaidJar: ["Multiple compatible variants found for library 'second':", - "Jar 'second:debug6Jar' [buildType:'debug', platform:'java6']", - "Jar 'second:release6Jar' [buildType:'release', platform:'java6']"]] + "Jar 'second:debug7Jar' [buildType:'debug', platform:'java7']", + "Jar 'second:release7Jar' [buildType:'release', platform:'java7']"]] and: outcome = errors ? 'fails' : 'succeeds' } + /** + * Asserts that the current JDK can compile for all target versions (e.g. if we are running with JDK8, we + * will not be able to compile for Java9). + */ + private static assertAllTargetVersionsAreSupported(List targetVersions) { + Assume.assumeTrue(targetVersions.max() <= Integer.valueOf(JavaVersion.current().majorVersion)) + } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilder.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilder.java index 799872510941e..bda782b7dfb25 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilder.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/JavaCompilerArgumentsBuilder.java @@ -20,10 +20,10 @@ import com.google.common.collect.Lists; import org.gradle.api.InvalidUserDataException; import org.gradle.api.JavaVersion; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.api.tasks.compile.ForkOptions; import org.gradle.util.GUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.util.ArrayList; @@ -31,7 +31,7 @@ import java.util.List; public class JavaCompilerArgumentsBuilder { - public static final Logger LOGGER = Logging.getLogger(JavaCompilerArgumentsBuilder.class); + public static final Logger LOGGER = LoggerFactory.getLogger(JavaCompilerArgumentsBuilder.class); public static final String USE_UNSHARED_COMPILER_TABLE_OPTION = "-XDuseUnsharedTable=true"; public static final String EMPTY_SOURCE_PATH_REF_DIR = "emptySourcePathRef"; diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationInitializer.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationInitializer.java index d190ac9d932c5..777b2d377a85f 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationInitializer.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilationInitializer.java @@ -16,12 +16,14 @@ package org.gradle.api.internal.tasks.compile.incremental; +import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.gradle.api.file.FileTree; import org.gradle.api.internal.file.FileOperations; import org.gradle.api.internal.tasks.compile.JavaCompileSpec; +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource; import org.gradle.api.internal.tasks.compile.incremental.recomp.RecompilationSpec; import org.gradle.api.tasks.util.PatternSet; import org.gradle.internal.Factory; @@ -30,7 +32,9 @@ import java.io.File; import java.util.Collection; import java.util.Collections; +import java.util.EnumMap; import java.util.List; +import java.util.Map; import java.util.Set; class IncrementalCompilationInitializer { @@ -52,13 +56,20 @@ public void initializeCompilation(JavaCompileSpec spec, RecompilationSpec recomp PatternSet classesToDelete = patternSetFactory.create(); PatternSet sourceToCompile = patternSetFactory.create(); - preparePatterns(recompilationSpec.getClassesToCompile(), classesToDelete, sourceToCompile); + prepareJavaPatterns(recompilationSpec.getClassesToCompile(), classesToDelete, sourceToCompile); spec.setSourceFiles(narrowDownSourcesToCompile(sourceTree, sourceToCompile)); includePreviousCompilationOutputOnClasspath(spec); addClassesToProcess(spec, recompilationSpec); deleteStaleFilesIn(classesToDelete, spec.getDestinationDir()); deleteStaleFilesIn(classesToDelete, spec.getCompileOptions().getAnnotationProcessorGeneratedSourcesDirectory()); deleteStaleFilesIn(classesToDelete, spec.getCompileOptions().getHeaderOutputDirectory()); + + Map resourcesToDelete = prepareResourcePatterns(recompilationSpec.getResourcesToGenerate(), patternSetFactory); + deleteStaleFilesIn(resourcesToDelete.get(GeneratedResource.Location.CLASS_OUTPUT), spec.getDestinationDir()); + // If the client has not set a location for SOURCE_OUTPUT, javac outputs those files to the CLASS_OUTPUT directory, so clean that instead. + deleteStaleFilesIn(resourcesToDelete.get(GeneratedResource.Location.SOURCE_OUTPUT), MoreObjects.firstNonNull(spec.getCompileOptions().getAnnotationProcessorGeneratedSourcesDirectory(), spec.getDestinationDir())); + // In the same situation with NATIVE_HEADER_OUTPUT, javac just NPEs. Don't bother. + deleteStaleFilesIn(resourcesToDelete.get(GeneratedResource.Location.NATIVE_HEADER_OUTPUT), spec.getCompileOptions().getHeaderOutputDirectory()); } private Iterable narrowDownSourcesToCompile(FileTree sourceTree, PatternSet sourceToCompile) { @@ -79,7 +90,7 @@ private void addClassesToProcess(JavaCompileSpec spec, RecompilationSpec recompi } private void deleteStaleFilesIn(PatternSet filesToDelete, final File destinationDir) { - if (destinationDir == null) { + if (filesToDelete == null || filesToDelete.isEmpty() || destinationDir == null) { return; } Set toDelete = fileOperations.fileTree(destinationDir).matching(filesToDelete).getFiles(); @@ -88,7 +99,7 @@ private void deleteStaleFilesIn(PatternSet filesToDelete, final File destination cleaner.execute(); } - private void preparePatterns(Collection staleClasses, PatternSet filesToDelete, PatternSet sourceToCompile) { + private void prepareJavaPatterns(Collection staleClasses, PatternSet filesToDelete, PatternSet sourceToCompile) { for (String staleClass : staleClasses) { String path = staleClass.replaceAll("\\.", "/"); filesToDelete.include(path.concat(".class")); @@ -102,4 +113,15 @@ private void preparePatterns(Collection staleClasses, PatternSet filesTo sourceToCompile.include(path.concat("$*.java")); } } + + private static Map prepareResourcePatterns(Collection staleResources, Factory patternSetFactory) { + Map resourcesByLocation = new EnumMap(GeneratedResource.Location.class); + for (GeneratedResource.Location location : GeneratedResource.Location.values()) { + resourcesByLocation.put(location, patternSetFactory.create()); + } + for (GeneratedResource resource : staleResources) { + resourcesByLocation.get(resource.getLocation()).include(resource.getPath()); + } + return resourcesByLocation; + } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilerDecorator.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilerDecorator.java index 761eea634aa6d..ecce74b91dbad 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilerDecorator.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalCompilerDecorator.java @@ -26,17 +26,17 @@ import org.gradle.api.internal.tasks.compile.incremental.recomp.PreviousCompilationData; import org.gradle.api.internal.tasks.compile.incremental.recomp.PreviousCompilationOutputAnalyzer; import org.gradle.api.internal.tasks.compile.incremental.recomp.RecompilationSpecProvider; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.api.tasks.incremental.IncrementalTaskInputs; import org.gradle.language.base.internal.compile.Compiler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Decorates a non-incremental Java compiler (like javac) so that it can be invoked incrementally. */ public class IncrementalCompilerDecorator { - private static final Logger LOG = Logging.getLogger(IncrementalCompilerDecorator.class); + private static final Logger LOG = LoggerFactory.getLogger(IncrementalCompilerDecorator.class); private final ClasspathSnapshotMaker classpathSnapshotMaker; private final TaskScopedCompileCaches compileCaches; private final CleaningJavaCompiler cleaningCompiler; diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalResultStoringCompiler.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalResultStoringCompiler.java index 2ece6241ede06..19adf72f6dabc 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalResultStoringCompiler.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/IncrementalResultStoringCompiler.java @@ -28,6 +28,7 @@ import org.gradle.api.internal.tasks.compile.incremental.classpath.ClasspathSnapshotProvider; import org.gradle.api.internal.tasks.compile.incremental.processing.AnnotationProcessingData; import org.gradle.api.internal.tasks.compile.incremental.processing.AnnotationProcessingResult; +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource; import org.gradle.api.internal.tasks.compile.incremental.recomp.PreviousCompilationData; import org.gradle.api.internal.tasks.compile.processing.AnnotationProcessorDeclaration; import org.gradle.api.tasks.WorkResult; @@ -80,14 +81,16 @@ private AnnotationProcessingData getAnnotationProcessingResult(JavaCompileSpec s AnnotationProcessingResult processingResult = ((JdkJavaCompilerResult) result).getAnnotationProcessingResult(); return convertProcessingResult(processingResult); } - return new AnnotationProcessingData(ImmutableMap.>of(), ImmutableSet.of(), ImmutableSet.of(), "the chosen compiler did not support incremental annotation processing"); + return new AnnotationProcessingData(ImmutableMap.>of(), ImmutableSet.of(), ImmutableSet.of(), ImmutableMap.>of(), ImmutableSet.of(), "the chosen compiler did not support incremental annotation processing"); } private AnnotationProcessingData convertProcessingResult(AnnotationProcessingResult processingResult) { Map> generatedTypesByOrigin = processingResult.getGeneratedTypesWithIsolatedOrigin(); + Map> generatedResourcesByOrigin = processingResult.getGeneratedResourcesWithIsolatedOrigin(); Set aggregatedTypes = processingResult.getAggregatedTypes(); Set aggregatingTypes = processingResult.getGeneratedAggregatingTypes(); - return new AnnotationProcessingData(intern(generatedTypesByOrigin), intern(aggregatedTypes), intern(aggregatingTypes), processingResult.getFullRebuildCause()); + Set aggregatingResources = processingResult.getGeneratedAggregatingResources(); + return new AnnotationProcessingData(intern(generatedTypesByOrigin), intern(aggregatedTypes), intern(aggregatingTypes), generatedResourcesByOrigin, aggregatingResources, processingResult.getFullRebuildCause()); } private Set intern(Set types) { diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/SelectiveCompiler.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/SelectiveCompiler.java index 6bf8a4a2e4ad5..58b43e58901c1 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/SelectiveCompiler.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/SelectiveCompiler.java @@ -24,18 +24,18 @@ import org.gradle.api.internal.tasks.compile.incremental.recomp.PreviousCompilation; import org.gradle.api.internal.tasks.compile.incremental.recomp.RecompilationSpec; import org.gradle.api.internal.tasks.compile.incremental.recomp.RecompilationSpecProvider; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.api.tasks.WorkResult; import org.gradle.api.tasks.incremental.IncrementalTaskInputs; import org.gradle.internal.time.Time; import org.gradle.internal.time.Timer; import org.gradle.language.base.internal.compile.Compiler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Collection; class SelectiveCompiler implements org.gradle.language.base.internal.compile.Compiler { - private static final Logger LOG = Logging.getLogger(SelectiveCompiler.class); + private static final Logger LOG = LoggerFactory.getLogger(SelectiveCompiler.class); private final IncrementalTaskInputs inputs; private final PreviousCompilation previousCompilation; private final CleaningJavaCompiler cleaningCompiler; diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassAnalysisSerializer.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassAnalysisSerializer.java index 5156626cfd57f..fc66e58958fa8 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassAnalysisSerializer.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/analyzer/ClassAnalysisSerializer.java @@ -44,8 +44,7 @@ public ClassAnalysis read(Decoder decoder) throws Exception { boolean relatedToAll = decoder.readBoolean(); Set classes = stringSetSerializer.read(decoder); IntSet constants = IntSetSerializer.INSTANCE.read(decoder); - Set superTypes = stringSetSerializer.read(decoder); - return new ClassAnalysis(className, classes, relatedToAll, constants, superTypes); + return new ClassAnalysis(className, classes, relatedToAll, constants); } @Override @@ -54,7 +53,6 @@ public void write(Encoder encoder, ClassAnalysis value) throws Exception { encoder.writeBoolean(value.isDependencyToAll()); stringSetSerializer.write(encoder, value.getClassDependencies()); IntSetSerializer.INSTANCE.write(encoder, value.getConstants()); - stringSetSerializer.write(encoder, value.getSuperTypes()); } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/asm/ClassDependenciesVisitor.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/asm/ClassDependenciesVisitor.java index a5b97897805ec..21a5d2156d54a 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/asm/ClassDependenciesVisitor.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/asm/ClassDependenciesVisitor.java @@ -21,8 +21,8 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import org.gradle.api.internal.cache.StringInterner; -import org.gradle.internal.classanalysis.AsmConstants; import org.gradle.api.internal.tasks.compile.incremental.deps.ClassAnalysis; +import org.gradle.internal.classanalysis.AsmConstants; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Label; @@ -41,7 +41,6 @@ public class ClassDependenciesVisitor extends ClassVisitor { private final MethodVisitor methodVisitor; private final FieldVisitor fieldVisitor; private final IntSet constants; - private final Set superTypes; private final Set types; private final Predicate typeFilter; private final StringInterner interner; @@ -54,7 +53,6 @@ private ClassDependenciesVisitor(Predicate typeFilter, ClassReader reade super(API); this.constants = new IntOpenHashSet(2); this.types = Sets.newHashSet(); - this.superTypes = Sets.newHashSet(); this.methodVisitor = new MethodVisitor(); this.fieldVisitor = new FieldVisitor(); this.retentionPolicyVisitor = new RetentionPolicyVisitor(); @@ -67,7 +65,7 @@ private ClassDependenciesVisitor(Predicate typeFilter, ClassReader reade public static ClassAnalysis analyze(String className, ClassReader reader, StringInterner interner) { ClassDependenciesVisitor visitor = new ClassDependenciesVisitor(new ClassRelevancyFilter(className), reader, interner); reader.accept(visitor, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - return new ClassAnalysis(interner.intern(className), visitor.getClassDependencies(), visitor.isDependencyToAll(), visitor.getConstants(), visitor.getSuperTypes()); + return new ClassAnalysis(interner.intern(className), visitor.getClassDependencies(), visitor.isDependencyToAll(), visitor.getConstants()); } @Override @@ -77,13 +75,11 @@ public void visit(int version, int access, String name, String signature, String // superName can be null if what we are analyzing is `java.lang.Object` // which can happen when a custom Java SDK is on classpath (typically, android.jar) String type = typeOfFromSlashyString(superName); - maybeAddSuperType(type); maybeAddDependentType(type); } for (String s : interfaces) { String interfaceType = typeOfFromSlashyString(s); maybeAddDependentType(interfaceType); - maybeAddSuperType(interfaceType); } } @@ -117,12 +113,6 @@ private void collectClassDependencies(ClassReader reader) { } } - protected void maybeAddSuperType(String type) { - if (typeFilter.apply(type)) { - superTypes.add(intern(type)); - } - } - protected void maybeAddDependentType(String type) { if (typeFilter.apply(type)) { types.add(intern(type)); @@ -137,10 +127,6 @@ protected String typeOfFromSlashyString(String slashyStyleDesc) { return Type.getObjectType(slashyStyleDesc).getClassName(); } - public Set getSuperTypes() { - return superTypes; - } - public Set getClassDependencies() { return types; } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathEntrySnapshot.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathEntrySnapshot.java index 7234271dfc4f5..2b3a294a318c1 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathEntrySnapshot.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathEntrySnapshot.java @@ -19,9 +19,8 @@ import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSets; -import org.gradle.api.internal.tasks.compile.incremental.deps.AffectedClasses; +import org.gradle.api.internal.tasks.compile.incremental.deps.ClassChanges; import org.gradle.api.internal.tasks.compile.incremental.deps.ClassSetAnalysis; -import org.gradle.api.internal.tasks.compile.incremental.deps.ClassSetAnalysisData; import org.gradle.api.internal.tasks.compile.incremental.deps.DependentsSet; import org.gradle.internal.hash.HashCode; @@ -49,7 +48,7 @@ public DependentsSet getAllClasses() { } result.add(className); } - return DependentsSet.dependents(result); + return DependentsSet.dependentClasses(result); } public IntSet getAllConstants(DependentsSet dependents) { @@ -70,38 +69,23 @@ public IntSet getRelevantConstants(ClasspathEntrySnapshot other, Set aff return result; } - public AffectedClasses getAffectedClassesSince(ClasspathEntrySnapshot other) { - DependentsSet affectedClasses = affectedSince(other); + public ClassChanges getChangedClassesSince(ClasspathEntrySnapshot other) { + Set modifiedClasses = modifiedSince(other); Set addedClasses = addedSince(other); - return new AffectedClasses(affectedClasses, addedClasses); + return new ClassChanges(modifiedClasses, addedClasses); } - private DependentsSet affectedSince(ClasspathEntrySnapshot other) { - final Set affected = new HashSet(); + private Set modifiedSince(ClasspathEntrySnapshot other) { + final Set modified = new HashSet(); for (Map.Entry otherClass : other.getHashes().entrySet()) { String otherClassName = otherClass.getKey(); HashCode otherClassBytes = otherClass.getValue(); HashCode thisClsBytes = getHashes().get(otherClassName); if (thisClsBytes == null || !thisClsBytes.equals(otherClassBytes)) { - affected.add(otherClassName); - DependentsSet dependents = other.getClassAnalysis().getRelevantDependents(otherClassName, IntSets.EMPTY_SET); - if (dependents.isDependencyToAll()) { - return dependents; - } - affected.addAll(dependents.getDependentClasses()); + modified.add(otherClassName); } } - for (String added : addedSince(other)) { - if (added.endsWith(ClassSetAnalysisData.PACKAGE_INFO)) { - affected.add(added); - DependentsSet dependents = other.getClassAnalysis().getRelevantDependents(added, IntSets.EMPTY_SET); - if (dependents.isDependencyToAll()) { - return dependents; - } - affected.addAll(dependents.getDependentClasses()); - } - } - return DependentsSet.dependents(affected); + return modified; } private Set addedSince(ClasspathEntrySnapshot other) { diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathSnapshot.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathSnapshot.java index f4451d9fa3e58..74af0d04b29aa 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathSnapshot.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathSnapshot.java @@ -16,8 +16,6 @@ package org.gradle.api.internal.tasks.compile.incremental.classpath; -import org.gradle.api.Action; - import java.io.File; import java.util.Collections; import java.util.LinkedHashMap; @@ -54,10 +52,4 @@ public boolean isAnyClassDuplicated(File classpathEntry) { ClasspathEntrySnapshot snapshot = getSnapshot(classpathEntry); return isAnyClassDuplicated(snapshot.getClasses()); } - - public void forEachSnapshot(Action action) { - for (ClasspathEntrySnapshot classpathEntrySnapshot : entrySnapshots.values()) { - action.execute(classpathEntrySnapshot); - } - } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathSnapshotMaker.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathSnapshotMaker.java index 946273ad28cfa..5c108dcdb68f1 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathSnapshotMaker.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathSnapshotMaker.java @@ -16,16 +16,16 @@ package org.gradle.api.internal.tasks.compile.incremental.classpath; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.time.Time; import org.gradle.internal.time.Timer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; public class ClasspathSnapshotMaker implements ClasspathSnapshotProvider { - private static final Logger LOG = Logging.getLogger(ClasspathSnapshotMaker.class); + private static final Logger LOG = LoggerFactory.getLogger(ClasspathSnapshotMaker.class); private final ClasspathSnapshotFactory classpathSnapshotFactory; diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/DefaultClasspathEntrySnapshotter.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/DefaultClasspathEntrySnapshotter.java index 329862e6632e4..aa4b696664613 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/DefaultClasspathEntrySnapshotter.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/classpath/DefaultClasspathEntrySnapshotter.java @@ -22,12 +22,12 @@ import org.gradle.api.internal.tasks.compile.incremental.analyzer.ClassDependenciesAnalyzer; import org.gradle.api.internal.tasks.compile.incremental.deps.ClassAnalysis; import org.gradle.api.internal.tasks.compile.incremental.deps.ClassDependentsAccumulator; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.IoActions; import org.gradle.internal.hash.FileHasher; import org.gradle.internal.hash.HashCode; import org.gradle.internal.hash.StreamHasher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.InputStream; @@ -36,7 +36,7 @@ import static org.gradle.internal.FileUtils.hasExtension; public class DefaultClasspathEntrySnapshotter { - private static final Logger LOGGER = Logging.getLogger(DefaultClasspathEntrySnapshotter.class); + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultClasspathEntrySnapshotter.class); private final FileHasher fileHasher; private final StreamHasher hasher; diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassAnalysis.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassAnalysis.java index 298f6102ab82f..43fbf93b44458 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassAnalysis.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassAnalysis.java @@ -30,14 +30,12 @@ public class ClassAnalysis { private final Set classDependencies; private final boolean dependencyToAll; private final IntSet constants; - private final Set superTypes; - public ClassAnalysis(String className, Set classDependencies, boolean dependencyToAll, IntSet constants, Set superTypes) { + public ClassAnalysis(String className, Set classDependencies, boolean dependencyToAll, IntSet constants) { this.className = className; this.classDependencies = ImmutableSet.copyOf(classDependencies); this.dependencyToAll = dependencyToAll; this.constants = constants.isEmpty() ? IntSets.EMPTY_SET : constants; - this.superTypes = ImmutableSet.copyOf(superTypes); } public String getClassName() { @@ -55,8 +53,4 @@ public IntSet getConstants() { public boolean isDependencyToAll() { return dependencyToAll; } - - public Set getSuperTypes() { - return superTypes; - } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/AffectedClasses.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassChanges.java similarity index 78% rename from subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/AffectedClasses.java rename to subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassChanges.java index 574e954b37966..6188904a6a85f 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/AffectedClasses.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassChanges.java @@ -18,18 +18,18 @@ import java.util.Set; -public class AffectedClasses { +public class ClassChanges { - private final DependentsSet altered; + private final Set modified; private final Set addedClasses; - public AffectedClasses(DependentsSet altered, Set addedClasses) { - this.altered = altered; + public ClassChanges(Set modified, Set addedClasses) { + this.modified = modified; this.addedClasses = addedClasses; } - public DependentsSet getAltered() { - return altered; + public Set getModified() { + return modified; } public Set getAdded() { diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassDependentsAccumulator.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassDependentsAccumulator.java index d915f986fc278..61a3307cebdc9 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassDependentsAccumulator.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassDependentsAccumulator.java @@ -17,7 +17,6 @@ package org.gradle.api.internal.tasks.compile.incremental.deps; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Multimap; @@ -35,14 +34,13 @@ public class ClassDependentsAccumulator { private final Map> dependents = new HashMap>(); private final ImmutableMap.Builder classesToConstants = ImmutableMap.builder(); private final Set seenClasses = Sets.newHashSet(); - private final Multimap parentToChildren = HashMultimap.create(); private String fullRebuildCause; public void addClass(ClassAnalysis classAnalysis) { - addClass(classAnalysis.getClassName(), classAnalysis.isDependencyToAll(), classAnalysis.getClassDependencies(), classAnalysis.getConstants(), classAnalysis.getSuperTypes()); + addClass(classAnalysis.getClassName(), classAnalysis.isDependencyToAll(), classAnalysis.getClassDependencies(), classAnalysis.getConstants()); } - public void addClass(String className, boolean dependencyToAll, Iterable classDependencies, IntSet constants, Set superTypes) { + public void addClass(String className, boolean dependencyToAll, Iterable classDependencies, IntSet constants) { if (seenClasses.contains(className)) { // same classes may be found in different classpath trees/jars // and we keep only the first one @@ -61,9 +59,6 @@ public void addClass(String className, boolean dependencyToAll, Iterable addDependency(dependency, className); } } - for (String superType : superTypes) { - parentToChildren.put(superType, className); - } } private Set rememberClass(String className) { @@ -85,7 +80,7 @@ Map getDependentsMap() { builder.put(s, DependentsSet.dependencyToAll()); } for (Map.Entry> entry : dependents.entrySet()) { - builder.put(entry.getKey(), DependentsSet.dependents(entry.getValue())); + builder.put(entry.getKey(), DependentsSet.dependentClasses(entry.getValue())); } return builder.build(); } @@ -105,7 +100,7 @@ public void fullRebuildNeeded(String fullRebuildCause) { } public ClassSetAnalysisData getAnalysis() { - return new ClassSetAnalysisData(ImmutableSet.copyOf(seenClasses), getDependentsMap(), getClassesToConstants(), asMap(parentToChildren), fullRebuildCause); + return new ClassSetAnalysisData(ImmutableSet.copyOf(seenClasses), getDependentsMap(), getClassesToConstants(), fullRebuildCause); } private static Map> asMap(Multimap multimap) { diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysis.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysis.java index 0acc432b7e964..f1f2243caf854 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysis.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysis.java @@ -21,6 +21,7 @@ import com.google.common.collect.Sets; import it.unimi.dsi.fastutil.ints.IntSet; import org.gradle.api.internal.tasks.compile.incremental.processing.AnnotationProcessingData; +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource; import java.util.HashSet; import java.util.Map; @@ -30,7 +31,8 @@ public class ClassSetAnalysis { private final ClassSetAnalysisData classAnalysis; private final AnnotationProcessingData annotationProcessingData; - private final ImmutableSetMultimap dependenciesFromAnnotationProcessing; + private final ImmutableSetMultimap classDependenciesFromAnnotationProcessing; + private final ImmutableSetMultimap resourceDependenciesFromAnnotationProcessing; public ClassSetAnalysis(ClassSetAnalysisData classAnalysis) { this(classAnalysis, new AnnotationProcessingData()); @@ -39,15 +41,24 @@ public ClassSetAnalysis(ClassSetAnalysisData classAnalysis) { public ClassSetAnalysis(ClassSetAnalysisData classAnalysis, AnnotationProcessingData annotationProcessingData) { this.classAnalysis = classAnalysis; this.annotationProcessingData = annotationProcessingData; - ImmutableSetMultimap.Builder dependenciesFromAnnotationProcessing = ImmutableSetMultimap.builder(); + ImmutableSetMultimap.Builder classDependenciesFromAnnotationProcessing = ImmutableSetMultimap.builder(); for (Map.Entry> entry : annotationProcessingData.getGeneratedTypesByOrigin().entrySet()) { for (String generated : entry.getValue()) { String origin = entry.getKey(); - dependenciesFromAnnotationProcessing.put(origin, generated); - dependenciesFromAnnotationProcessing.put(generated, origin); + classDependenciesFromAnnotationProcessing.put(origin, generated); + classDependenciesFromAnnotationProcessing.put(generated, origin); } } - this.dependenciesFromAnnotationProcessing = dependenciesFromAnnotationProcessing.build(); + this.classDependenciesFromAnnotationProcessing = classDependenciesFromAnnotationProcessing.build(); + + ImmutableSetMultimap.Builder resourceDependenciesFromAnnotationProcessing = ImmutableSetMultimap.builder(); + for (Map.Entry> entry : annotationProcessingData.getGeneratedResourcesByOrigin().entrySet()) { + for (GeneratedResource generated : entry.getValue()) { + String origin = entry.getKey(); + resourceDependenciesFromAnnotationProcessing.put(origin, generated); + } + } + this.resourceDependenciesFromAnnotationProcessing = resourceDependenciesFromAnnotationProcessing.build(); } public ClassSetAnalysis withAnnotationProcessingData(AnnotationProcessingData annotationProcessingData) { @@ -55,22 +66,28 @@ public ClassSetAnalysis withAnnotationProcessingData(AnnotationProcessingData an } public DependentsSet getRelevantDependents(Iterable classes, IntSet constants) { - Set result = null; + Set resultClasses = null; + Set resultResources = null; for (String cls : classes) { DependentsSet d = getRelevantDependents(cls, constants); if (d.isDependencyToAll()) { return d; } Set dependentClasses = d.getDependentClasses(); - if (dependentClasses.isEmpty()) { + Set dependentResources = d.getDependentResources(); + if (dependentClasses.isEmpty() && dependentResources.isEmpty()) { continue; } - if (result == null) { - result = Sets.newLinkedHashSet(); + if (resultClasses == null) { + resultClasses = Sets.newLinkedHashSet(); } - result.addAll(dependentClasses); + resultClasses.addAll(dependentClasses); + if (resultResources == null) { + resultResources = Sets.newLinkedHashSet(); + } + resultResources.addAll(dependentResources); } - return result == null ? DependentsSet.empty() : DependentsSet.dependents(result); + return resultClasses == null ? DependentsSet.empty() : DependentsSet.dependents(resultClasses, resultResources); } public DependentsSet getRelevantDependents(String className, IntSet constants) { @@ -85,15 +102,19 @@ public DependentsSet getRelevantDependents(String className, IntSet constants) { if (!constants.isEmpty()) { return DependentsSet.dependencyToAll(); } - Set dependingOnAllOthers = annotationProcessingData.getGeneratedTypesDependingOnAllOthers(); - if (deps.getDependentClasses().isEmpty() && dependingOnAllOthers.isEmpty()) { + Set classesDependingOnAllOthers = annotationProcessingData.getGeneratedTypesDependingOnAllOthers(); + Set resourcesDependingOnAllOthers = annotationProcessingData.getGeneratedResourcesDependingOnAllOthers(); + if (deps.getDependentClasses().isEmpty() && classesDependingOnAllOthers.isEmpty() && resourcesDependingOnAllOthers.isEmpty()) { return deps; } - Set result = new HashSet(); - recurseDependents(new HashSet(), result, deps.getDependentClasses()); - recurseDependents(new HashSet(), result, dependingOnAllOthers); - result.remove(className); - return DependentsSet.dependents(result); + + Set resultClasses = new HashSet(); + Set resultResources = new HashSet(resourcesDependingOnAllOthers); + recurseDependentClasses(new HashSet(), resultClasses, resultResources, deps.getDependentClasses()); + recurseDependentClasses(new HashSet(), resultClasses, resultResources, classesDependingOnAllOthers); + resultClasses.remove(className); + + return DependentsSet.dependents(resultClasses, resultResources); } public Set getTypesToReprocess() { @@ -104,17 +125,22 @@ public boolean isDependencyToAll(String className) { return classAnalysis.getDependents(className).isDependencyToAll(); } - private void recurseDependents(Set visited, Set result, Iterable dependentClasses) { + /** + * Recursively accumulate dependent classes and resources. Dependent classes discovered can themselves be used to query + * further dependents, while resources are just data accumulated along the way. + */ + private void recurseDependentClasses(Set visitedClasses, Set resultClasses, Set resultResources, Iterable dependentClasses) { for (String d : dependentClasses) { - if (!visited.add(d)) { + if (!visitedClasses.add(d)) { continue; } if (!isNestedClass(d)) { - result.add(d); + resultClasses.add(d); } DependentsSet currentDependents = getDependents(d); if (!currentDependents.isDependencyToAll()) { - recurseDependents(visited, result, currentDependents.getDependentClasses()); + resultResources.addAll(currentDependents.getDependentResources()); + recurseDependentClasses(visitedClasses, resultClasses, resultResources, currentDependents.getDependentClasses()); } } } @@ -124,11 +150,12 @@ private DependentsSet getDependents(String className) { if (dependents.isDependencyToAll()) { return dependents; } - ImmutableSet additionalDeps = dependenciesFromAnnotationProcessing.get(className); - if (additionalDeps.isEmpty()) { + ImmutableSet additionalClassDeps = classDependenciesFromAnnotationProcessing.get(className); + ImmutableSet additionalResourceDeps = resourceDependenciesFromAnnotationProcessing.get(className); + if (additionalClassDeps.isEmpty() && additionalResourceDeps.isEmpty()) { return dependents; } - return DependentsSet.dependents(Sets.union(dependents.getDependentClasses(), additionalDeps)); + return DependentsSet.dependents(Sets.union(dependents.getDependentClasses(), additionalClassDeps), Sets.union(dependents.getDependentResources(), additionalResourceDeps)); } private boolean isNestedClass(String d) { diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisData.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisData.java index 79c90bb3088ad..d482b0fddca4c 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisData.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisData.java @@ -29,7 +29,6 @@ import org.gradle.internal.serialize.IntSetSerializer; import java.io.IOException; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Set; @@ -40,14 +39,12 @@ public class ClassSetAnalysisData { private final Set classes; private final Map dependents; private final Map classesToConstants; - private final Map> classesToChildren; private final String fullRebuildCause; - public ClassSetAnalysisData(Set classes, Map dependents, Map classesToConstants, Map> classesToChildren, String fullRebuildCause) { + public ClassSetAnalysisData(Set classes, Map dependents, Map classesToConstants, String fullRebuildCause) { this.classes = classes; this.dependents = dependents; this.classesToConstants = classesToConstants; - this.classesToChildren = classesToChildren; this.fullRebuildCause = fullRebuildCause; } @@ -71,7 +68,7 @@ private DependentsSet getDependentsOfPackage(String packageName) { typesInPackage.add(type); } } - return DependentsSet.dependents(typesInPackage); + return DependentsSet.dependentClasses(typesInPackage); } public IntSet getConstants(String className) { @@ -82,11 +79,6 @@ public IntSet getConstants(String className) { return integers; } - public Set getChildren(String className) { - Set children = classesToChildren.get(className); - return children == null ? Collections.emptySet() : children; - } - public static class Serializer extends AbstractSerializer { private final StringInterner interner; @@ -121,21 +113,9 @@ public ClassSetAnalysisData read(Decoder decoder) throws Exception { classesToConstantsBuilder.put(className, constants); } - count = decoder.readSmallInt(); - ImmutableMap.Builder> classNameToChildren = ImmutableMap.builder(); - for (int i = 0; i < count; i++) { - String parent = readClassName(decoder, classNameMap); - int nameCount = decoder.readSmallInt(); - ImmutableSet.Builder namesBuilder = ImmutableSet.builder(); - for (int j = 0; j < nameCount; j++) { - namesBuilder.add(readClassName(decoder, classNameMap)); - } - classNameToChildren.put(parent, namesBuilder.build()); - } - String fullRebuildCause = decoder.readNullableString(); - return new ClassSetAnalysisData(classes.build(), dependentsBuilder.build(), classesToConstantsBuilder.build(), classNameToChildren.build(), fullRebuildCause); + return new ClassSetAnalysisData(classes.build(), dependentsBuilder.build(), classesToConstantsBuilder.build(), fullRebuildCause); } @Override @@ -157,16 +137,6 @@ public void write(Encoder encoder, ClassSetAnalysisData value) throws Exception writeClassName(entry.getKey(), classNameMap, encoder); IntSetSerializer.INSTANCE.write(encoder, entry.getValue()); } - - encoder.writeSmallInt(value.classesToChildren.size()); - for (Map.Entry> entry : value.classesToChildren.entrySet()) { - writeClassName(entry.getKey(), classNameMap, encoder); - encoder.writeSmallInt(entry.getValue().size()); - for (String className : entry.getValue()) { - writeClassName(className, classNameMap, encoder); - } - } - encoder.writeNullableString(value.fullRebuildCause); } @@ -180,7 +150,7 @@ private DependentsSet readDependentsSet(Decoder decoder, Map cl for (int i = 0; i < count; i++) { builder.add(readClassName(decoder, classNameMap)); } - return DependentsSet.dependents(builder.build()); + return DependentsSet.dependentClasses(builder.build()); } private void writeDependentSet(DependentsSet dependentsSet, Map classNameMap, Encoder encoder) throws IOException { diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/DependentsSet.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/DependentsSet.java index fe782ef8b0e81..7e1a35b1f453f 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/DependentsSet.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/deps/DependentsSet.java @@ -17,6 +17,7 @@ package org.gradle.api.internal.tasks.compile.incremental.deps; import com.google.common.collect.ImmutableSet; +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource; import javax.annotation.Nullable; import java.util.Collections; @@ -24,19 +25,23 @@ public abstract class DependentsSet { - public static DependentsSet dependents(String... dependentClasses) { + public static DependentsSet dependentClasses(String... dependentClasses) { if (dependentClasses.length == 0) { return empty(); } else { - return new DefaultDependentsSet(ImmutableSet.copyOf(dependentClasses)); + return new DefaultDependentsSet(ImmutableSet.copyOf(dependentClasses), Collections.emptySet()); } } - public static DependentsSet dependents(Set dependentClasses) { - if (dependentClasses.isEmpty()) { + public static DependentsSet dependentClasses(Set dependentClasses) { + return dependents(dependentClasses, Collections.emptySet()); + } + + public static DependentsSet dependents(Set dependentClasses, Set dependentResources) { + if (dependentClasses.isEmpty() && dependentResources.isEmpty()) { return empty(); } else { - return new DefaultDependentsSet(ImmutableSet.copyOf(dependentClasses)); + return new DefaultDependentsSet(ImmutableSet.copyOf(dependentClasses), ImmutableSet.copyOf(dependentResources)); } } @@ -54,6 +59,8 @@ public static DependentsSet empty() { public abstract Set getDependentClasses(); + public abstract Set getDependentResources(); + public abstract boolean isDependencyToAll(); public abstract @Nullable String getDescription(); @@ -69,6 +76,11 @@ public Set getDependentClasses() { return Collections.emptySet(); } + @Override + public Set getDependentResources() { + return Collections.emptySet(); + } + @Override public boolean isDependencyToAll() { return false; @@ -84,9 +96,11 @@ public String getDescription() { private static class DefaultDependentsSet extends DependentsSet { private final Set dependentClasses; + private final Set dependentResources; - private DefaultDependentsSet(Set dependentClasses) { + private DefaultDependentsSet(Set dependentClasses, Set dependentResources) { this.dependentClasses = dependentClasses; + this.dependentResources = dependentResources; } @Override @@ -94,6 +108,11 @@ public Set getDependentClasses() { return dependentClasses; } + @Override + public Set getDependentResources() { + return dependentResources; + } + @Override public boolean isDependencyToAll() { return false; @@ -123,6 +142,11 @@ public Set getDependentClasses() { throw new UnsupportedOperationException("This instance of dependents set does not have dependent classes information."); } + @Override + public Set getDependentResources() { + throw new UnsupportedOperationException("This instance of dependents set does not have dependent resources information."); + } + @Override public boolean isDependencyToAll() { return true; diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessingData.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessingData.java index 40562c4ff6d42..d26101c63f3d5 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessingData.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessingData.java @@ -33,16 +33,22 @@ public class AnnotationProcessingData { private final Map> generatedTypesByOrigin; private final Set aggregatedTypes; private final Set generatedTypesDependingOnAllOthers; + private final Map> generatedResourcesByOrigin; + private final Set generatedResourcesDependingOnAllOthers; private final String fullRebuildCause; public AnnotationProcessingData() { - this(ImmutableMap.>of(), ImmutableSet.of(), ImmutableSet.of(), null); + this(ImmutableMap.>of(), ImmutableSet.of(), ImmutableSet.of(), ImmutableMap.>of(), ImmutableSet.of(), null); } - public AnnotationProcessingData(Map> generatedTypesByOrigin, Set aggregatedTypes, Set generatedTypesDependingOnAllOthers, String fullRebuildCause) { + public AnnotationProcessingData(Map> generatedTypesByOrigin, Set aggregatedTypes, Set generatedTypesDependingOnAllOthers, Map> generatedResourcesByOrigin, Set generatedResourcesDependingOnAllOthers, String fullRebuildCause) { + this.generatedTypesByOrigin = ImmutableMap.copyOf(generatedTypesByOrigin); this.aggregatedTypes = ImmutableSet.copyOf(aggregatedTypes); this.generatedTypesDependingOnAllOthers = ImmutableSet.copyOf(generatedTypesDependingOnAllOthers); + this.generatedResourcesByOrigin = ImmutableMap.copyOf(generatedResourcesByOrigin); + this.generatedResourcesDependingOnAllOthers = ImmutableSet.copyOf(generatedResourcesDependingOnAllOthers); this.fullRebuildCause = fullRebuildCause; } @@ -58,6 +64,14 @@ public Set getGeneratedTypesDependingOnAllOthers() { return generatedTypesDependingOnAllOthers; } + public Map> getGeneratedResourcesByOrigin() { + return generatedResourcesByOrigin; + } + + public Set getGeneratedResourcesDependingOnAllOthers() { + return generatedResourcesDependingOnAllOthers; + } + public String getFullRebuildCause() { return fullRebuildCause; } @@ -65,11 +79,17 @@ public String getFullRebuildCause() { public static final class Serializer extends AbstractSerializer { private final SetSerializer typesSerializer; private final MapSerializer> generatedTypesSerializer; + private final SetSerializer resourcesSerializer; + private final MapSerializer> generatedResourcesSerializer; public Serializer(StringInterner interner) { InterningStringSerializer stringSerializer = new InterningStringSerializer(interner); typesSerializer = new SetSerializer(stringSerializer); generatedTypesSerializer = new MapSerializer>(stringSerializer, typesSerializer); + + GeneratedResourceSerializer resourceSerializer = new GeneratedResourceSerializer(stringSerializer); + this.resourcesSerializer = new SetSerializer(resourceSerializer); + this.generatedResourcesSerializer = new MapSerializer>(stringSerializer, resourcesSerializer); } @Override @@ -78,7 +98,10 @@ public AnnotationProcessingData read(Decoder decoder) throws Exception { Set aggregatedTypes = typesSerializer.read(decoder); Set generatedTypesDependingOnAllOthers = typesSerializer.read(decoder); String fullRebuildCause = decoder.readNullableString(); - return new AnnotationProcessingData(generatedTypes, aggregatedTypes, generatedTypesDependingOnAllOthers, fullRebuildCause); + Map> generatedResources = generatedResourcesSerializer.read(decoder); + Set generatedResourcesDependingOnAllOthers = resourcesSerializer.read(decoder); + + return new AnnotationProcessingData(generatedTypes, aggregatedTypes, generatedTypesDependingOnAllOthers, generatedResources, generatedResourcesDependingOnAllOthers, fullRebuildCause); } @Override @@ -87,6 +110,8 @@ public void write(Encoder encoder, AnnotationProcessingData value) throws Except typesSerializer.write(encoder, value.aggregatedTypes); typesSerializer.write(encoder, value.generatedTypesDependingOnAllOthers); encoder.writeNullableString(value.fullRebuildCause); + generatedResourcesSerializer.write(encoder, value.generatedResourcesByOrigin); + resourcesSerializer.write(encoder, value.generatedResourcesDependingOnAllOthers); } } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessingResult.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessingResult.java index 37056a805ded6..de53eb32d012c 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessingResult.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessingResult.java @@ -31,8 +31,10 @@ public class AnnotationProcessingResult implements Serializable { private final Map> generatedTypesByOrigin = new LinkedHashMap>(); + private final Map> generatedResourcesByOrigin = new LinkedHashMap>(); private final Set aggregatedTypes = new HashSet(); private final Set generatedTypesDependingOnAllOthers = new HashSet(); + private final Set getGeneratedResourcesDependingOnAllOthers = new HashSet(); private final List annotationProcessorResults = new ArrayList(); private String fullRebuildCause; @@ -47,6 +49,17 @@ public void addGeneratedType(String name, Set originatingElements) { } } + public void addGeneratedResource(GeneratedResource resource, Set originatingElements) { + for (String originatingElement : originatingElements) { + Set derived = generatedResourcesByOrigin.get(originatingElement); + if (derived == null) { + derived = new LinkedHashSet(); + generatedResourcesByOrigin.put(originatingElement, derived); + } + derived.add(resource); + } + } + /** * Contains the types generated by isolating annotation processors, grouped by the type they were generated from. */ @@ -54,6 +67,13 @@ public Map> getGeneratedTypesWithIsolatedOrigin() { return generatedTypesByOrigin; } + /** + * Contains the resources generated by isolating annotation processors, grouped by the type they were generated from. + */ + public Map> getGeneratedResourcesWithIsolatedOrigin() { + return generatedResourcesByOrigin; + } + /** * Contains the types that aggregating annotation processors registered themselves for. * These types need to be reprocessed no matter what source changes are made to ensure that the generated types contain all relevant information. @@ -70,6 +90,14 @@ public Set getGeneratedAggregatingTypes() { return generatedTypesDependingOnAllOthers; } + /** + * Contains the resources that aggregating annotation processors generated. + * These resources need to be recreated on any source change, because it may not be clear where these resources came from and whether they are now stale. + */ + public Set getGeneratedAggregatingResources() { + return getGeneratedResourcesDependingOnAllOthers; + } + public void setFullRebuildCause(String fullRebuildCause) { this.fullRebuildCause = fullRebuildCause; } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessorResult.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessorResult.java index 2d64a03030a6f..3ea68201cdd5c 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessorResult.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/AnnotationProcessorResult.java @@ -55,6 +55,10 @@ public void addGeneratedType(String name, Set originatingElements) { processingResult.addGeneratedType(name, originatingElements); } + public void addGeneratedResource(GeneratedResource resource, Set originatingElements) { + processingResult.addGeneratedResource(resource, originatingElements); + } + public Set getAggregatedTypes() { return processingResult.getAggregatedTypes(); } @@ -63,6 +67,10 @@ public Set getGeneratedAggregatingTypes() { return processingResult.getGeneratedAggregatingTypes(); } + public Set getGeneratedAggregatingResources() { + return processingResult.getGeneratedAggregatingResources(); + } + public void setFullRebuildCause(String fullRebuildCause) { processingResult.setFullRebuildCause(fullRebuildCause); } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/GeneratedResource.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/GeneratedResource.java new file mode 100644 index 0000000000000..d98ff3e36aaa7 --- /dev/null +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/GeneratedResource.java @@ -0,0 +1,99 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.compile.incremental.processing; + +import com.google.common.base.Objects; + +import javax.annotation.Nullable; +import javax.tools.JavaFileManager; +import javax.tools.StandardLocation; +import java.io.Serializable; + +/** + * Uniquely identifies a resource that was generated by an annotation processor. + */ +public final class GeneratedResource implements Serializable { + /** + * The supported locations into which generated resources may be placed. + */ + public enum Location { + CLASS_OUTPUT, + SOURCE_OUTPUT, + NATIVE_HEADER_OUTPUT,; + + /** + * @return null if the given location is not supported. + */ + @Nullable + public static Location from(JavaFileManager.Location location) { + if (location instanceof StandardLocation) { + switch ((StandardLocation) location) { + case CLASS_OUTPUT: + return CLASS_OUTPUT; + case SOURCE_OUTPUT: + return SOURCE_OUTPUT; + case NATIVE_HEADER_OUTPUT: + return NATIVE_HEADER_OUTPUT; + } + } + return null; + } + } + + private final Location location; + private final String path; + + public GeneratedResource(Location location, CharSequence pkg, CharSequence relativeName) { + this(location, pkg.length() == 0 ? relativeName.toString() : pkg.toString().replace('.', '/') + '/' + relativeName); + } + + public GeneratedResource(Location location, String path) { + this.location = location; + this.path = path; + } + + public Location getLocation() { + return location; + } + + public String getPath() { + return path; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GeneratedResource that = (GeneratedResource) o; + return location == that.location && + path.equals(that.path); + } + + @Override + public int hashCode() { + return Objects.hashCode(location, path); + } + + @Override + public String toString() { + return path + " in " + location; + } +} diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/GeneratedResourceSerializer.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/GeneratedResourceSerializer.java new file mode 100644 index 0000000000000..da3ec093b9e86 --- /dev/null +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/processing/GeneratedResourceSerializer.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.api.internal.tasks.compile.incremental.processing; + +import org.gradle.internal.serialize.AbstractSerializer; +import org.gradle.internal.serialize.BaseSerializerFactory; +import org.gradle.internal.serialize.Decoder; +import org.gradle.internal.serialize.Encoder; +import org.gradle.internal.serialize.Serializer; + +import java.io.EOFException; + +public class GeneratedResourceSerializer extends AbstractSerializer { + private static final Serializer LOCATION_SERIALIZER = new BaseSerializerFactory().getSerializerFor(GeneratedResource.Location.class); + private final Serializer stringSerializer; + + public GeneratedResourceSerializer(Serializer stringSerializer) { + this.stringSerializer = stringSerializer; + } + + @Override + public GeneratedResource read(Decoder decoder) throws EOFException, Exception { + return new GeneratedResource(LOCATION_SERIALIZER.read(decoder), stringSerializer.read(decoder)); + } + + @Override + public void write(Encoder encoder, GeneratedResource value) throws Exception { + LOCATION_SERIALIZER.write(encoder, value.getLocation()); + stringSerializer.write(encoder, value.getPath()); + } +} diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/ClasspathChangeDependentsFinder.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/ClasspathChangeDependentsFinder.java index ac202471f37a0..dc5e8d3f0be50 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/ClasspathChangeDependentsFinder.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/ClasspathChangeDependentsFinder.java @@ -18,10 +18,9 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import org.gradle.api.Action; import org.gradle.api.internal.tasks.compile.incremental.classpath.ClasspathEntrySnapshot; import org.gradle.api.internal.tasks.compile.incremental.classpath.ClasspathSnapshot; -import org.gradle.api.internal.tasks.compile.incremental.deps.AffectedClasses; +import org.gradle.api.internal.tasks.compile.incremental.deps.ClassChanges; import org.gradle.api.internal.tasks.compile.incremental.deps.ClassSetAnalysisData; import org.gradle.api.internal.tasks.compile.incremental.deps.DependentsSet; import org.gradle.api.tasks.incremental.InputFileDetails; @@ -42,73 +41,84 @@ public ClasspathChangeDependentsFinder(ClasspathSnapshot classpathSnapshot, Prev public DependentsSet getActualDependents(InputFileDetails entryChangeDetails, File classpathEntry) { if (entryChangeDetails.isAdded()) { - if (classpathSnapshot.isAnyClassDuplicated(classpathEntry)) { - //at least one of the classes from the new entry is already present in classpath - //to avoid calculation which class gets on the classpath first, rebuild all - return DependentsSet.dependencyToAll("at least one of the classes of '" + classpathEntry + "' is already present in classpath"); - } else { - //none of the new classes in the entry are duplicated on classpath, don't rebuild - return DependentsSet.empty(); - } + return handleAdded(classpathEntry); } - final ClasspathEntrySnapshot previous = previousCompilation.getClasspathEntrySnapshot(entryChangeDetails.getFile()); + + ClasspathEntrySnapshot previous = previousCompilation.getClasspathEntrySnapshot(entryChangeDetails.getFile()); if (previous == null) { - //we don't know what classes were dependents of the entry in the previous build - //for example, a class with a constant might have changed into a class without a constant - we need to rebuild everything return DependentsSet.dependencyToAll("missing classpath entry snapshot of '" + classpathEntry + "' from previous build"); + } else if (entryChangeDetails.isRemoved()) { + return handleRemoved(previous); + } else if (entryChangeDetails.isModified()) { + return handleModified(classpathEntry, previous); + } else { + throw new IllegalArgumentException("Unknown input file details provided: " + entryChangeDetails); } + } - if (entryChangeDetails.isRemoved()) { - DependentsSet allClasses = previous.getAllClasses(); - if (allClasses.isDependencyToAll()) { - return allClasses; - } - //recompile all dependents of all the classes from this entry - return previousCompilation.getDependents(allClasses.getDependentClasses(), previous.getAllConstants(allClasses)); + private DependentsSet handleAdded(File classpathEntry) { + if (classpathSnapshot.isAnyClassDuplicated(classpathEntry)) { + return DependentsSet.dependencyToAll("at least one of the classes of '" + classpathEntry + "' is already present in classpath"); + } else { + return DependentsSet.empty(); } + } - if (entryChangeDetails.isModified()) { - final ClasspathEntrySnapshot currentSnapshot = classpathSnapshot.getSnapshot(classpathEntry); - AffectedClasses affected = currentSnapshot.getAffectedClassesSince(previous); - DependentsSet altered = affected.getAltered(); - if (altered.isDependencyToAll()) { - //at least one of the classes changed in the entry is a 'dependency-to-all' - return altered; - } + private DependentsSet handleRemoved(ClasspathEntrySnapshot previous) { + DependentsSet allClasses = previous.getAllClasses(); + if (allClasses.isDependencyToAll()) { + return allClasses; + } + DependentsSet affectedOnClasspath = collectDependentsFromClasspath(allClasses.getDependentClasses()); + if (affectedOnClasspath.isDependencyToAll()) { + return affectedOnClasspath; + } else { + return previousCompilation.getDependents(affectedOnClasspath.getDependentClasses(), previous.getAllConstants(affectedOnClasspath)); + } + } - if (classpathSnapshot.isAnyClassDuplicated(affected.getAdded())) { - //A new duplicate class on classpath. As we don't fancy-handle classpath order right now, we don't know which class is on classpath first. - //For safe measure rebuild everything - return DependentsSet.dependencyToAll("at least one of the classes of modified classpath entry '" + classpathEntry + "' is already present in the classpath"); - } + private DependentsSet handleModified(File classpathEntry, final ClasspathEntrySnapshot previous) { + final ClasspathEntrySnapshot currentSnapshot = classpathSnapshot.getSnapshot(classpathEntry); + ClassChanges classChanges = currentSnapshot.getChangedClassesSince(previous); + + if (classpathSnapshot.isAnyClassDuplicated(classChanges.getAdded())) { + return DependentsSet.dependencyToAll("at least one of the classes of modified classpath entry '" + classpathEntry + "' is already present in the classpath"); + } + + DependentsSet affectedOnClasspath = collectDependentsFromClasspath(Sets.union(classChanges.getModified(), classChanges.getAdded())); + if (affectedOnClasspath.isDependencyToAll()) { + return affectedOnClasspath; + } else { + return previousCompilation.getDependents(affectedOnClasspath.getDependentClasses(), currentSnapshot.getRelevantConstants(previous, affectedOnClasspath.getDependentClasses())); + } + } - //recompile all dependents of the classes changed in the entry - - final Set dependentClasses = Sets.newHashSet(altered.getDependentClasses()); - final Deque queue = Lists.newLinkedList(dependentClasses); - while (!queue.isEmpty()) { - final String dependentClass = queue.poll(); - classpathSnapshot.forEachSnapshot(new Action() { - @Override - public void execute(ClasspathEntrySnapshot classpathEntrySnapshot) { - if (classpathEntrySnapshot != previous) { - // we need to find classes in other entries that would potentially extend classes changed - // in the current snapshot (they are intermediates) - ClassSetAnalysisData data = classpathEntrySnapshot.getData().getClassAnalysis(); - Set children = data.getChildren(dependentClass); - for (String child : children) { - if (dependentClasses.add(child)) { - queue.add(child); - } - } + private DependentsSet collectDependentsFromClasspath(Set modified) { + final Set dependentClasses = Sets.newHashSet(modified); + final Deque queue = Lists.newLinkedList(dependentClasses); + while (!queue.isEmpty()) { + final String dependentClass = queue.poll(); + for (File entry : classpathSnapshot.getEntries()) { + DependentsSet dependents = collectDependentsFromClasspathEntry(dependentClass, entry); + if (dependents.isDependencyToAll()) { + return dependents; + } else { + for (String intermediate : dependents.getDependentClasses()) { + if (dependentClasses.add(intermediate)) { + queue.add(intermediate); } } - }); + } } - return previousCompilation.getDependents(dependentClasses, currentSnapshot.getRelevantConstants(previous, dependentClasses)); } + return DependentsSet.dependentClasses(dependentClasses); + } - throw new IllegalArgumentException("Unknown input file details provided: " + entryChangeDetails); + private DependentsSet collectDependentsFromClasspathEntry(String dependentClass, File entry) { + ClasspathEntrySnapshot entrySnapshot = classpathSnapshot.getSnapshot(entry); + ClassSetAnalysisData data = entrySnapshot.getData().getClassAnalysis(); + return data.getDependents(dependentClass); } + } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/ClasspathEntryChangeProcessor.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/ClasspathEntryChangeProcessor.java index 929a66601d282..79ae0642fb55d 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/ClasspathEntryChangeProcessor.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/ClasspathEntryChangeProcessor.java @@ -35,5 +35,6 @@ public void processChange(InputFileDetails input, RecompilationSpec spec) { return; } spec.getClassesToCompile().addAll(actualDependents.getDependentClasses()); + spec.getResourcesToGenerate().addAll(actualDependents.getDependentResources()); } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/CompilationSourceDirs.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/CompilationSourceDirs.java index 12395295b0271..a24120226da1c 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/CompilationSourceDirs.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/CompilationSourceDirs.java @@ -21,8 +21,9 @@ import org.gradle.api.internal.file.FileCollectionInternal; import org.gradle.api.internal.file.FileCollectionLeafVisitor; import org.gradle.api.internal.file.FileTreeInternal; -import org.gradle.api.logging.Logging; import org.gradle.api.tasks.util.PatternSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.util.List; @@ -36,7 +37,7 @@ */ @NonNullApi public class CompilationSourceDirs { - private static final org.gradle.api.logging.Logger LOG = Logging.getLogger(CompilationSourceDirs.class); + private static final Logger LOG = LoggerFactory.getLogger(CompilationSourceDirs.class); private final FileTreeInternal sources; private SourceRoots sourceRoots; diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/JavaChangeProcessor.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/JavaChangeProcessor.java index f74df2bdd9526..5d5f4e990aaab 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/JavaChangeProcessor.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/JavaChangeProcessor.java @@ -39,5 +39,6 @@ public void processChange(InputFileDetails input, RecompilationSpec spec) { return; } spec.getClassesToCompile().addAll(actualDependents.getDependentClasses()); + spec.getResourcesToGenerate().addAll(actualDependents.getDependentResources()); } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/PreviousCompilationOutputAnalyzer.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/PreviousCompilationOutputAnalyzer.java index c15de806d3407..2d7f0277595fb 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/PreviousCompilationOutputAnalyzer.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/PreviousCompilationOutputAnalyzer.java @@ -21,19 +21,19 @@ import org.gradle.api.internal.tasks.compile.incremental.classpath.ClasspathEntrySnapshot; import org.gradle.api.internal.tasks.compile.incremental.classpath.DefaultClasspathEntrySnapshotter; import org.gradle.api.internal.tasks.compile.incremental.deps.ClassSetAnalysis; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.internal.hash.FileHasher; import org.gradle.internal.hash.HashCode; import org.gradle.internal.hash.StreamHasher; import org.gradle.internal.time.Time; import org.gradle.internal.time.Timer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; //TODO reuse cached result from downstream users of our classes directory public class PreviousCompilationOutputAnalyzer { - private static final Logger LOG = Logging.getLogger(PreviousCompilationOutputAnalyzer.class); + private static final Logger LOG = LoggerFactory.getLogger(PreviousCompilationOutputAnalyzer.class); private final DefaultClasspathEntrySnapshotter snapshotter; diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/RecompilationSpec.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/RecompilationSpec.java index abc22d7b348a5..56617e8c1ea4c 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/RecompilationSpec.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/RecompilationSpec.java @@ -16,6 +16,8 @@ package org.gradle.api.internal.tasks.compile.incremental.recomp; +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource; + import java.io.File; import java.util.Collection; import java.util.LinkedHashSet; @@ -24,6 +26,7 @@ public class RecompilationSpec { private final Collection classesToCompile = new NormalizingClassNamesSet(); private final Collection classesToProcess = new NormalizingClassNamesSet(); + private final Collection resourcesToGenerate = new LinkedHashSet(); private String fullRebuildCause; public Collection getClassesToCompile() { @@ -34,6 +37,10 @@ public Collection getClassesToProcess() { return classesToProcess; } + public Collection getResourcesToGenerate() { + return resourcesToGenerate; + } + public boolean isBuildNeeded() { return isFullRebuildNeeded() || !classesToCompile.isEmpty() || !classesToProcess.isEmpty(); } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/RecompilationSpecProvider.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/RecompilationSpecProvider.java index 2e5978d8d7d8e..9f761bf81ec13 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/RecompilationSpecProvider.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/incremental/recomp/RecompilationSpecProvider.java @@ -18,8 +18,9 @@ import org.gradle.api.internal.tasks.compile.incremental.classpath.ClasspathEntrySnapshot; import org.gradle.api.internal.tasks.compile.incremental.classpath.ClasspathSnapshot; -import org.gradle.internal.change.FileChange; +import org.gradle.internal.change.DefaultFileChange; import org.gradle.internal.file.FileType; +import org.gradle.internal.fingerprint.impl.IgnoredPathFingerprintingStrategy; import org.gradle.internal.util.Alignment; import java.io.File; @@ -52,10 +53,12 @@ private void processClasspathChanges(CurrentCompilation current, PreviousCompila for (Alignment fileAlignment : alignment) { switch (fileAlignment.getKind()) { case added: - classpathEntryChangeProcessor.processChange(FileChange.added(fileAlignment.getCurrentValue().getAbsolutePath(), "classpathEntry", FileType.RegularFile), spec); + DefaultFileChange added = DefaultFileChange.added(fileAlignment.getCurrentValue().getAbsolutePath(), "classpathEntry", FileType.RegularFile, IgnoredPathFingerprintingStrategy.IGNORED_PATH); + classpathEntryChangeProcessor.processChange(added, spec); break; case removed: - classpathEntryChangeProcessor.processChange(FileChange.removed(fileAlignment.getPreviousValue().getAbsolutePath(), "classpathEntry", FileType.RegularFile), spec); + DefaultFileChange removed = DefaultFileChange.removed(fileAlignment.getPreviousValue().getAbsolutePath(), "classpathEntry", FileType.RegularFile, IgnoredPathFingerprintingStrategy.IGNORED_PATH); + classpathEntryChangeProcessor.processChange(removed, spec); break; case transformed: // If we detect a transformation in the classpath, we need to recompile, because we could typically be facing the case where @@ -67,7 +70,8 @@ private void processClasspathChanges(CurrentCompilation current, PreviousCompila ClasspathEntrySnapshot previousSnapshot = previous.getClasspathEntrySnapshot(key); ClasspathEntrySnapshot snapshot = currentSnapshots.getSnapshot(key); if (previousSnapshot == null || !snapshot.getHash().equals(previousSnapshot.getHash())) { - classpathEntryChangeProcessor.processChange(FileChange.modified(key.getAbsolutePath(), "classpathEntry", FileType.RegularFile, FileType.RegularFile), spec); + DefaultFileChange modified = DefaultFileChange.modified(key.getAbsolutePath(), "classpathEntry", FileType.RegularFile, FileType.RegularFile, IgnoredPathFingerprintingStrategy.IGNORED_PATH); + classpathEntryChangeProcessor.processChange(modified, spec); } break; } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/processing/AggregatingProcessingStrategy.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/processing/AggregatingProcessingStrategy.java index 3c7eba69777ee..60cb3a55465f9 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/processing/AggregatingProcessingStrategy.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/processing/AggregatingProcessingStrategy.java @@ -17,10 +17,12 @@ package org.gradle.api.internal.tasks.compile.processing; import org.gradle.api.internal.tasks.compile.incremental.processing.AnnotationProcessorResult; +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileManager; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Set; @@ -67,4 +69,14 @@ private void recordAggregatedTypes(Set supportedAnnotationTypes, Set supportedAnnotationTypes, Set originatingTypes = ElementUtils.getTopLevelTypeNames(originatingElements); + int size = originatingTypes.size(); + if (size != 1) { + result.setFullRebuildCause("the generated resource '" + generatedResource + "' must have exactly one originating element, but had " + size); + } + result.addGeneratedResource(generatedResource, originatingTypes); + } } diff --git a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/processing/NonIncrementalProcessingStrategy.java b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/processing/NonIncrementalProcessingStrategy.java index 0dd437771ca36..f4c920bf4a929 100644 --- a/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/processing/NonIncrementalProcessingStrategy.java +++ b/subprojects/language-java/src/main/java/org/gradle/api/internal/tasks/compile/processing/NonIncrementalProcessingStrategy.java @@ -21,6 +21,7 @@ import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileManager; import java.util.Set; import static org.gradle.api.internal.tasks.compile.incremental.processing.IncrementalAnnotationProcessorType.UNKNOWN; @@ -47,4 +48,9 @@ public void recordProcessingInputs(Set supportedAnnotationTypes, Set { - private final static Logger LOG = Logging.getLogger(JavadocGenerator.class); + private final static Logger LOG = LoggerFactory.getLogger(JavadocGenerator.class); private final ExecActionFactory execActionFactory; diff --git a/subprojects/language-java/src/main/java/org/gradle/language/java/internal/JavaLanguagePluginServiceRegistry.java b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/JavaLanguagePluginServiceRegistry.java index d96e65f607af4..5a8494aaf5c27 100644 --- a/subprojects/language-java/src/main/java/org/gradle/language/java/internal/JavaLanguagePluginServiceRegistry.java +++ b/subprojects/language-java/src/main/java/org/gradle/language/java/internal/JavaLanguagePluginServiceRegistry.java @@ -24,7 +24,6 @@ import org.gradle.api.internal.tasks.compile.incremental.cache.GeneralCompileCaches; import org.gradle.api.internal.tasks.compile.processing.AnnotationProcessorDetector; import org.gradle.api.internal.tasks.compile.tooling.JavaCompileTaskSuccessResultPostProcessor; -import org.gradle.api.logging.Logging; import org.gradle.api.logging.configuration.LoggingConfiguration; import org.gradle.api.logging.configuration.ShowStacktrace; import org.gradle.cache.internal.FileContentCacheFactory; @@ -40,6 +39,7 @@ import org.gradle.tooling.events.OperationType; import org.gradle.tooling.internal.provider.BuildClientSubscriptions; import org.gradle.tooling.internal.provider.SubscribableBuildActionRunnerRegistration; +import org.slf4j.LoggerFactory; import java.util.Collections; @@ -86,7 +86,7 @@ public void configure(ServiceRegistration registration, ComponentTypeRegistry co } public AnnotationProcessorDetector createAnnotationProcessorDetector(FileContentCacheFactory cacheFactory, LoggingConfiguration loggingConfiguration) { - return new AnnotationProcessorDetector(cacheFactory, Logging.getLogger(AnnotationProcessorDetector.class), loggingConfiguration.getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS); + return new AnnotationProcessorDetector(cacheFactory, LoggerFactory.getLogger(AnnotationProcessorDetector.class), loggingConfiguration.getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS); } } diff --git a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/JavaHomeBasedJavaCompilerFactoryTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/JavaHomeBasedJavaCompilerFactoryTest.groovy index 520d5f57bc8bd..b8ae8816b33d8 100644 --- a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/JavaHomeBasedJavaCompilerFactoryTest.groovy +++ b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/JavaHomeBasedJavaCompilerFactoryTest.groovy @@ -17,7 +17,6 @@ package org.gradle.api.internal.tasks.compile import org.gradle.internal.Factory -import org.gradle.internal.SystemProperties import org.gradle.test.fixtures.file.TestFile import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider import org.junit.Rule @@ -58,9 +57,8 @@ class JavaHomeBasedJavaCompilerFactoryTest extends Specification { } def "creates Java compiler for mismatching Java home directory"() { - File originalJavaHomeDir = SystemProperties.instance.javaHomeDir + File originalJavaHomeDir = new File(System.properties['java.home'] as String) TestFile realJavaHome = temporaryFolder.file('my/test/java/home/real') - TestFile javaHomeFromToolProvidersPointOfView = temporaryFolder.file('my/test/java/home/toolprovider') when: JavaCompiler expectedJavaCompiler = factory.create() @@ -68,10 +66,10 @@ class JavaHomeBasedJavaCompilerFactoryTest extends Specification { then: 1 * currentJvmJavaHomeFactory.create() >> realJavaHome 1 * systemJavaCompilerFactory.create() >> { - assert SystemProperties.instance.javaHomeDir == realJavaHome + assert new File(System.properties['java.home'] as String) == realJavaHome javaCompiler } javaCompiler == expectedJavaCompiler - originalJavaHomeDir == SystemProperties.instance.javaHomeDir + originalJavaHomeDir == new File(System.properties['java.home'] as String) } } diff --git a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathEntrySnapshotTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathEntrySnapshotTest.groovy index fd1010c11a2a5..ff5c1a2f4fa07 100644 --- a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathEntrySnapshotTest.groovy +++ b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/classpath/ClasspathEntrySnapshotTest.groovy @@ -17,12 +17,9 @@ package org.gradle.api.internal.tasks.compile.incremental.classpath import org.gradle.api.internal.tasks.compile.incremental.deps.ClassSetAnalysisData -import org.gradle.api.internal.tasks.compile.incremental.deps.DependentsSet import org.gradle.internal.hash.HashCode import spock.lang.Specification -import static org.gradle.api.internal.tasks.compile.incremental.deps.DependentsSet.dependents - class ClasspathEntrySnapshotTest extends Specification { def analysis = Stub(ClassSetAnalysisData) @@ -31,8 +28,8 @@ class ClasspathEntrySnapshotTest extends Specification { new ClasspathEntrySnapshot(new ClasspathEntrySnapshotData(HashCode.fromInt(0x1234), hashes, a)) } - private DependentsSet altered(ClasspathEntrySnapshot s1, ClasspathEntrySnapshot s2) { - s1.getAffectedClassesSince(s2).altered + private Set altered(ClasspathEntrySnapshot s1, ClasspathEntrySnapshot s2) { + s1.getChangedClassesSince(s2).modified } def "knows when there are no affected classes since some other snapshot"() { @@ -40,7 +37,7 @@ class ClasspathEntrySnapshotTest extends Specification { ClasspathEntrySnapshot s2 = snapshot(["A": HashCode.fromInt(0xaa), "B": HashCode.fromInt(0xbb)], analysis) expect: - altered(s1, s2).dependentClasses.isEmpty() + altered(s1, s2).isEmpty() } def "knows when there are extra/missing classes since some other snapshot"() { @@ -48,8 +45,8 @@ class ClasspathEntrySnapshotTest extends Specification { ClasspathEntrySnapshot s2 = snapshot(["A": HashCode.fromInt(0xaa)], analysis) expect: - altered(s1, s2).dependentClasses.isEmpty() //ignore class additions - altered(s2, s1).dependentClasses == ["B", "C"] as Set + altered(s1, s2).isEmpty() //ignore class additions + altered(s2, s1) == ["B", "C"] as Set } def "knows when there are changed classes since other snapshot"() { @@ -57,46 +54,8 @@ class ClasspathEntrySnapshotTest extends Specification { ClasspathEntrySnapshot s2 = snapshot(["A": HashCode.fromInt(0xaa), "B": HashCode.fromInt(0xbbbb)], analysis) expect: - altered(s1, s2).dependentClasses == ["B"] as Set - altered(s2, s1).dependentClasses == ["B", "C"] as Set - } - - def "knows when transitive class is affected transitively via class change"() { - def analysis = Stub(ClassSetAnalysisData) - ClasspathEntrySnapshot s1 = snapshot(["A": HashCode.fromInt(0xaa), "B": HashCode.fromInt(0xbb), "C": HashCode.fromInt(0xcc)], analysis) - ClasspathEntrySnapshot s2 = snapshot(["A": HashCode.fromInt(0xaa), "B": HashCode.fromInt(0xbb), "C": HashCode.fromInt(0xcccc)], analysis) - - analysis.getDependents("C") >> dependents("B") - analysis.getDependents("B") >> dependents() - - expect: - altered(s1, s2).dependentClasses == ["B", "C"] as Set - altered(s2, s1).dependentClasses == ["B", "C"] as Set - } - - def "knows when transitive class is affected transitively via class removal"() { - def analysis = Stub(ClassSetAnalysisData) - ClasspathEntrySnapshot s1 = snapshot(["A": HashCode.fromInt(0xaa), "B": HashCode.fromInt(0xbb), "C": HashCode.fromInt(0xcc)], analysis) - ClasspathEntrySnapshot s2 = snapshot(["A": HashCode.fromInt(0xaa), "B": HashCode.fromInt(0xbb)], analysis) - - analysis.getDependents("C") >> dependents("B") - analysis.getDependents("B") >> dependents() - - expect: - altered(s1, s2).dependentClasses.isEmpty() - altered(s2, s1).dependentClasses == ["B", "C"] as Set - } - - def "knows when class is dependency to all"() { - def analysis = Mock(ClassSetAnalysisData) - ClasspathEntrySnapshot s1 = snapshot(["A": HashCode.fromInt(0xaa), "B": HashCode.fromInt(0xbb)], analysis) - ClasspathEntrySnapshot s2 = snapshot(["A": HashCode.fromInt(0xaa), "B": HashCode.fromInt(0xbbbb)], analysis) - - analysis.getDependents("B") >> DependentsSet.dependencyToAll() - - expect: - altered(s1, s2).isDependencyToAll() - altered(s2, s1).isDependencyToAll() + altered(s1, s2) == ["B"] as Set + altered(s2, s1) == ["B", "C"] as Set } def "knows added classes"() { @@ -105,8 +64,8 @@ class ClasspathEntrySnapshotTest extends Specification { ClasspathEntrySnapshot s3 = snapshot([:], analysis) expect: - s1.getAffectedClassesSince(s2).added == ["B", "C"] as Set - s2.getAffectedClassesSince(s1).added == [] as Set - s1.getAffectedClassesSince(s3).added == ["A", "B", "C"] as Set + s1.getChangedClassesSince(s2).added == ["B", "C"] as Set + s2.getChangedClassesSince(s1).added == [] as Set + s1.getChangedClassesSince(s3).added == ["A", "B", "C"] as Set } } diff --git a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassDependentsAccumulatorTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassDependentsAccumulatorTest.groovy index ba71a3b5ea0da..1e96836bf2f9d 100644 --- a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassDependentsAccumulatorTest.groovy +++ b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassDependentsAccumulatorTest.groovy @@ -31,9 +31,9 @@ class ClassDependentsAccumulatorTest extends Specification { def "remembers if class is dependency to all"() { // a -> b -> c - accumulator.addClass("a", false, ["b"], IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("b", true, ["c"], IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("c", false, ["a"] as Set, IntSets.EMPTY_SET, [] as Set) + accumulator.addClass("a", false, ["b"], IntSets.EMPTY_SET) + accumulator.addClass("b", true, ["c"], IntSets.EMPTY_SET) + accumulator.addClass("c", false, ["a"] as Set, IntSets.EMPTY_SET) expect: !accumulator.dependentsMap.a.dependencyToAll @@ -43,9 +43,9 @@ class ClassDependentsAccumulatorTest extends Specification { def "remembers if class declares non-private constants"() { // a -> b -> c - accumulator.addClass("a", false, ["b"], new IntOpenHashSet(1, 2, 3, 5, 8), [] as Set) - accumulator.addClass("b", false, ["c"], new IntOpenHashSet([0, 8]), [] as Set) - accumulator.addClass("c", false, [], new IntOpenHashSet([3, 4]), [] as Set) + accumulator.addClass("a", false, ["b"], new IntOpenHashSet(1, 2, 3, 5, 8)) + accumulator.addClass("b", false, ["c"], new IntOpenHashSet([0, 8])) + accumulator.addClass("c", false, [], new IntOpenHashSet([3, 4])) expect: accumulator.classesToConstants.get('a') == [1, 2, 3, 5, 8] as Set @@ -54,10 +54,10 @@ class ClassDependentsAccumulatorTest extends Specification { } def "accumulates dependents"() { - accumulator.addClass("d", true, ['x'], IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("a", false, ["b", "c"], IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("b", true, ["c", "a"], IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("c", false, [] as Set, IntSets.EMPTY_SET, [] as Set) + accumulator.addClass("d", true, ['x'], IntSets.EMPTY_SET) + accumulator.addClass("a", false, ["b", "c"], IntSets.EMPTY_SET) + accumulator.addClass("b", true, ["c", "a"], IntSets.EMPTY_SET) + accumulator.addClass("c", false, [] as Set, IntSets.EMPTY_SET) expect: accumulator.dependentsMap.a.dependentClasses == ['b'] as Set @@ -68,33 +68,33 @@ class ClassDependentsAccumulatorTest extends Specification { } def "creates keys for all encountered classes which are dependency to another"() { - accumulator.addClass("a", false, ["x"], IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("b", true, ["a", "b"], IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("c", true, [] as Set, IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("e", false, [] as Set, IntSets.EMPTY_SET, [] as Set) + accumulator.addClass("a", false, ["x"], IntSets.EMPTY_SET) + accumulator.addClass("b", true, ["a", "b"], IntSets.EMPTY_SET) + accumulator.addClass("c", true, [] as Set, IntSets.EMPTY_SET) + accumulator.addClass("e", false, [] as Set, IntSets.EMPTY_SET) expect: accumulator.dependentsMap.keySet() == ["a", "b", "c", "x"] as Set } def "knows when class is dependent to all if that class is added first"() { - accumulator.addClass("b", true, [] as Set, IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("a", false, ["b"], IntSets.EMPTY_SET, [] as Set) + accumulator.addClass("b", true, [] as Set, IntSets.EMPTY_SET) + accumulator.addClass("a", false, ["b"], IntSets.EMPTY_SET) expect: accumulator.dependentsMap.b.dependencyToAll } def "knows when class is dependent to all even if that class is added last"() { - accumulator.addClass("a", false, ["b"], IntSets.EMPTY_SET, [] as Set) - accumulator.addClass("b", true, [] as Set, IntSets.EMPTY_SET, [] as Set) + accumulator.addClass("a", false, ["b"], IntSets.EMPTY_SET) + accumulator.addClass("b", true, [] as Set, IntSets.EMPTY_SET) expect: accumulator.dependentsMap.b.dependencyToAll } def "filters out self dependencies"() { - accumulator.addClass("a", false, ["a", "b"], IntSets.EMPTY_SET, [] as Set) + accumulator.addClass("a", false, ["a", "b"], IntSets.EMPTY_SET) expect: accumulator.dependentsMap["b"].dependentClasses == ["a"] as Set diff --git a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisDataSerializerTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisDataSerializerTest.groovy index 447fc6256faf8..bf945f4aeb0f3 100644 --- a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisDataSerializerTest.groovy +++ b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisDataSerializerTest.groovy @@ -26,7 +26,7 @@ import spock.lang.Specification import spock.lang.Subject import static org.gradle.api.internal.tasks.compile.incremental.deps.DependentsSet.dependencyToAll -import static org.gradle.api.internal.tasks.compile.incremental.deps.DependentsSet.dependents +import static org.gradle.api.internal.tasks.compile.incremental.deps.DependentsSet.dependentClasses class ClassSetAnalysisDataSerializerTest extends Specification { @@ -34,10 +34,9 @@ class ClassSetAnalysisDataSerializerTest extends Specification { def "serializes"() { def data = new ClassSetAnalysisData(["A", "B", "C", "D"] as Set, - ["A": dependents("B", "C"), "B": dependents("C"), "C": dependents(), "D": dependencyToAll(),], + ["A": dependentClasses("B", "C"), "B": dependentClasses("C"), "C": dependentClasses(), "D": dependencyToAll(),], [C: new IntOpenHashSet([1, 2]) as IntSet, D: IntSets.EMPTY_SET] - , - ['A': ['SA'] as Set, B: ['SB1', 'SB2'] as Set], "Because" + ,"Because" ) def os = new ByteArrayOutputStream() def e = new OutputStreamBackedEncoder(os) @@ -56,7 +55,6 @@ class ClassSetAnalysisDataSerializerTest extends Specification { read.dependents["D"].dependencyToAll read.classesToConstants == [C: [1,2] as Set, D: [] as Set] - read.classesToChildren == ['A': ['SA'] as Set, B: ['SB1', 'SB2'] as Set] read.fullRebuildCause == "Because" } } diff --git a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisTest.groovy index 0d2c15da10be4..8c4617ed2bf08 100644 --- a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisTest.groovy +++ b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/incremental/deps/ClassSetAnalysisTest.groovy @@ -27,10 +27,10 @@ class ClassSetAnalysisTest extends Specification { ClassSetAnalysis analysis(Map dependents, Map classToConstants = [:], - Map> classesToChildren = [:], DependentsSet aggregatedTypes = empty(), DependentsSet dependentsOnAll = empty(), String fullRebuildCause = null) { + DependentsSet aggregatedTypes = empty(), DependentsSet dependentsOnAll = empty(), String fullRebuildCause = null) { new ClassSetAnalysis( - new ClassSetAnalysisData(dependents.keySet(), dependents, classToConstants, classesToChildren, fullRebuildCause), - new AnnotationProcessingData([:], aggregatedTypes.dependentClasses, dependentsOnAll.dependentClasses, null) + new ClassSetAnalysisData(dependents.keySet(), dependents, classToConstants, fullRebuildCause), + new AnnotationProcessingData([:], aggregatedTypes.dependentClasses, dependentsOnAll.dependentClasses, [:], dependentsOnAll.dependentResources, null) ) } @@ -65,9 +65,9 @@ class ClassSetAnalysisTest extends Specification { def "recurses nested dependencies"() { def a = analysis([ - "Foo": dependents("Bar"), - "Bar": dependents("Baz"), - "Baz": dependents(), + "Foo": dependentClasses("Bar"), + "Bar": dependentClasses("Baz"), + "Baz": dependentClasses(), ]) def deps = a.getRelevantDependents("Foo", IntSets.EMPTY_SET) @@ -79,11 +79,11 @@ class ClassSetAnalysisTest extends Specification { def "recurses multiple dependencies"() { def a = analysis([ - "a": dependents("b", "c"), - "b": dependents("d"), - "c": dependents("e"), - "d": dependents(), - "e": dependents() + "a": dependentClasses("b", "c"), + "b": dependentClasses("d"), + "c": dependentClasses("e"), + "d": dependentClasses(), + "e": dependentClasses() ]) def deps = a.getRelevantDependents("a", IntSets.EMPTY_SET) @@ -93,7 +93,7 @@ class ClassSetAnalysisTest extends Specification { def "removes self from dependents"() { def a = analysis([ - "Foo": dependents("Foo") + "Foo": dependentClasses("Foo") ]) def deps = a.getRelevantDependents("Foo", IntSets.EMPTY_SET) @@ -103,9 +103,9 @@ class ClassSetAnalysisTest extends Specification { def "handles dependency cycles"() { def a = analysis([ - "Foo": dependents("Bar"), - "Bar": dependents("Baz"), - "Baz": dependents("Foo"), + "Foo": dependentClasses("Bar"), + "Bar": dependentClasses("Baz"), + "Baz": dependentClasses("Foo"), ]) def deps = a.getRelevantDependents("Foo", IntSets.EMPTY_SET) @@ -115,10 +115,10 @@ class ClassSetAnalysisTest extends Specification { def "recurses but filters out inner classes"() { def a = analysis([ - "a": dependents('a$b', 'c'), - 'a$b': dependents('d'), - "c": dependents(), - "d": dependents(), + "a": dependentClasses('a$b', 'c'), + 'a$b': dependentClasses('d'), + "c": dependentClasses(), + "d": dependentClasses(), ]) def deps = a.getRelevantDependents("a", IntSets.EMPTY_SET) @@ -128,9 +128,9 @@ class ClassSetAnalysisTest extends Specification { def "handles cycles with inner classes"() { def a = analysis([ - "a": dependents('a$b'), - 'a$b': dependents('a$b', 'c'), - "c": dependents() + "a": dependentClasses('a$b'), + 'a$b': dependentClasses('a$b', 'c'), + "c": dependentClasses() ]) def deps = a.getRelevantDependents("a", IntSets.EMPTY_SET) @@ -140,9 +140,9 @@ class ClassSetAnalysisTest extends Specification { def "provides dependents of all input classes"() { def a = analysis([ - "A": dependents("B"), "B": dependents(), - "C": dependents("D"), "D": dependents(), - "E": dependents("D"), "F": dependents(), + "A": dependentClasses("B"), "B": dependentClasses(), + "C": dependentClasses("D"), "D": dependentClasses(), + "E": dependentClasses("D"), "F": dependentClasses(), ]) def deps = a.getRelevantDependents(["A", "E"], IntSets.EMPTY_SET) @@ -152,9 +152,9 @@ class ClassSetAnalysisTest extends Specification { def "provides recursive dependents of all input classes"() { def a = analysis([ - "A": dependents("B"), "B": dependents("C"), "C": dependents(), - "D": dependents("E"), "E": dependents(), - "F": dependents("G"), "G": dependents(), + "A": dependentClasses("B"), "B": dependentClasses("C"), "C": dependentClasses(), + "D": dependentClasses("E"), "E": dependentClasses(), + "F": dependentClasses("G"), "G": dependentClasses(), ]) def deps = a.getRelevantDependents(["A", "D"], IntSets.EMPTY_SET) @@ -164,8 +164,8 @@ class ClassSetAnalysisTest extends Specification { def "some classes may depend on any change"() { def a = analysis([ - "A": dependents("B"), "B": empty(), "DependsOnAny" : dependents("C") - ], [:], [:], empty(), dependents("DependsOnAny") ) + "A": dependentClasses("B"), "B": empty(), "DependsOnAny" : dependentClasses("C") + ], [:], empty(), dependentClasses("DependsOnAny") ) def deps = a.getRelevantDependents(["A"], IntSets.EMPTY_SET) expect: @@ -174,9 +174,9 @@ class ClassSetAnalysisTest extends Specification { def "knows when any of the input classes is a dependency to all"() { def a = analysis([ - "A": dependents("B"), "B": dependents(), + "A": dependentClasses("B"), "B": dependentClasses(), "C": dependentSet(true, []), - "D": dependents("E"), "E": dependents(), + "D": dependentClasses("E"), "E": dependentClasses(), ]) def deps = a.getRelevantDependents(["A", "C", "will not be reached"], IntSets.EMPTY_SET) @@ -186,7 +186,7 @@ class ClassSetAnalysisTest extends Specification { def "knows when input class is a dependency to all"() { def a = analysis([ - "A": dependents("B"), "B": dependents(), + "A": dependentClasses("B"), "B": dependentClasses(), "C": dependentSet(true, []), ]) expect: @@ -197,14 +197,14 @@ class ClassSetAnalysisTest extends Specification { def "all classes are dependencies to all if a full rebuild cause is given"() { def a = analysis( - [:], [:], [:], empty(), empty(), "Some cause" + [:], [:], empty(), empty(), "Some cause" ) expect: a.isDependencyToAll("DoesNotMatter") } - private static DependentsSet dependentSet(boolean dependencyToAll, Collection dependentClasses) { - dependencyToAll ? DependentsSet.dependencyToAll() : dependents(dependentClasses as Set) + private static DependentsSet dependentSet(boolean dependencyToAll, Collection classes) { + dependencyToAll ? DependentsSet.dependencyToAll() : dependentClasses(classes as Set) } } diff --git a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/AggregatingFilerTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/AggregatingFilerTest.groovy index 77acb4c672ff9..f3dbb6fb99c9c 100644 --- a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/AggregatingFilerTest.groovy +++ b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/AggregatingFilerTest.groovy @@ -17,6 +17,9 @@ package org.gradle.api.internal.tasks.compile.processing import org.gradle.api.internal.tasks.compile.incremental.processing.AnnotationProcessorResult +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource + +import javax.tools.StandardLocation class AggregatingFilerTest extends IncrementalFilerTest { @@ -28,6 +31,7 @@ class AggregatingFilerTest extends IncrementalFilerTest { def "can have zero originating elements"() { when: filer.createSourceFile("Foo") + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt") then: !result.fullRebuildCause @@ -36,6 +40,7 @@ class AggregatingFilerTest extends IncrementalFilerTest { def "can have many originating elements"() { when: filer.createSourceFile("Foo", type("Bar"), type("Baz")) + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt", type("Bar"), type("Baz")) then: !result.fullRebuildCause @@ -46,8 +51,39 @@ class AggregatingFilerTest extends IncrementalFilerTest { filer.createSourceFile("Foo", pkg("pkg"), type("A"), methodInside("B")) filer.createSourceFile("Bar", type("B")) + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt", pkg("pkg"), type("A"), methodInside("B")) + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "bar.txt", type("B")) + then: result.generatedTypesWithIsolatedOrigin.isEmpty() result.generatedAggregatingTypes == ["Foo", "Bar"] as Set + + result.generatedResourcesWithIsolatedOrigin.isEmpty() + result.generatedAggregatingResources == [sourceResource("foo.txt"), sourceResource("bar.txt")] as Set + } + + def "handles resources in the three StandardLocation output locations"() { + when: + filer.createResource(inputLocation, "com.enterprise.software", "foo.txt", type("A")) + + then: + result.generatedAggregatingResources == [new GeneratedResource(resultLocation, "com/enterprise/software/foo.txt")] as Set + + where: + inputLocation | resultLocation + StandardLocation.SOURCE_OUTPUT | GeneratedResource.Location.SOURCE_OUTPUT + StandardLocation.CLASS_OUTPUT | GeneratedResource.Location.CLASS_OUTPUT + StandardLocation.NATIVE_HEADER_OUTPUT | GeneratedResource.Location.NATIVE_HEADER_OUTPUT + } + + def "resources with same path but different location are distinct"() { + when: + filer.createResource(StandardLocation.SOURCE_OUTPUT, "com.enterprise.software", "foo.txt", type("A")) + filer.createResource(StandardLocation.CLASS_OUTPUT, "com.enterprise.software", "foo.txt", type("A")) + filer.createResource(StandardLocation.NATIVE_HEADER_OUTPUT, "com.enterprise.software", "foo.txt", type("A")) + + then: + result.generatedAggregatingResources == [GeneratedResource.Location.SOURCE_OUTPUT, GeneratedResource.Location.CLASS_OUTPUT, GeneratedResource.Location.NATIVE_HEADER_OUTPUT] + .collect { new GeneratedResource(it, "com/enterprise/software/foo.txt") } as Set } } diff --git a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/IncrementalFilerTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/IncrementalFilerTest.groovy index fab4004d2e518..b7b3397e5e99f 100644 --- a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/IncrementalFilerTest.groovy +++ b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/IncrementalFilerTest.groovy @@ -18,6 +18,7 @@ package org.gradle.api.internal.tasks.compile.processing import org.gradle.api.internal.tasks.compile.incremental.processing.AnnotationProcessingResult import org.gradle.api.internal.tasks.compile.incremental.processing.AnnotationProcessorResult +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource import spock.lang.Specification import javax.annotation.processing.Filer @@ -25,7 +26,6 @@ import javax.lang.model.element.ExecutableElement import javax.lang.model.element.Name import javax.lang.model.element.PackageElement import javax.lang.model.element.TypeElement -import javax.tools.StandardLocation abstract class IncrementalFilerTest extends Specification { Filer delegate = Stub(Filer) @@ -38,14 +38,6 @@ abstract class IncrementalFilerTest extends Specification { abstract IncrementalProcessingStrategy getStrategy(AnnotationProcessorResult result) - def "does a full rebuild when trying to write resources"() { - when: - filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt") - - then: - result.fullRebuildCause == "an annotation processor generated a resource" - } - PackageElement pkg(String packageName) { Stub(PackageElement) { getQualifiedName() >> Stub(Name) { @@ -69,4 +61,8 @@ abstract class IncrementalFilerTest extends Specification { getEnclosingElement() >> type(typeName) } } + + GeneratedResource sourceResource(String path) { + new GeneratedResource(GeneratedResource.Location.SOURCE_OUTPUT, path) + } } diff --git a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/IsolatingFilerTest.groovy b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/IsolatingFilerTest.groovy index 5ce16bfe2cae0..6c1bef760abda 100644 --- a/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/IsolatingFilerTest.groovy +++ b/subprojects/language-java/src/test/groovy/org/gradle/api/internal/tasks/compile/processing/IsolatingFilerTest.groovy @@ -17,6 +17,9 @@ package org.gradle.api.internal.tasks.compile.processing import org.gradle.api.internal.tasks.compile.incremental.processing.AnnotationProcessorResult +import org.gradle.api.internal.tasks.compile.incremental.processing.GeneratedResource + +import javax.tools.StandardLocation class IsolatingFilerTest extends IncrementalFilerTest { @@ -25,7 +28,7 @@ class IsolatingFilerTest extends IncrementalFilerTest { new IsolatingProcessingStrategy(result) } - def "does a full rebuild when no originating elements are given"() { + def "does a full rebuild when no originating elements are given for a type"() { when: filer.createSourceFile("Foo") @@ -33,7 +36,15 @@ class IsolatingFilerTest extends IncrementalFilerTest { result.fullRebuildCause == "the generated type 'Foo' must have exactly one originating element, but had 0" } - def "does a full rebuild when too many originating elements are given"() { + def "does a full rebuild when no originating elements are given for a resource"() { + when: + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt") + + then: + result.fullRebuildCause == "the generated resource 'foo.txt in SOURCE_OUTPUT' must have exactly one originating element, but had 0" + } + + def "does a full rebuild when too many originating elements are given for a type"() { when: filer.createSourceFile("Foo", type("Bar"), type("Baz")) @@ -41,9 +52,18 @@ class IsolatingFilerTest extends IncrementalFilerTest { result.fullRebuildCause == "the generated type 'Foo' must have exactly one originating element, but had 2" } - def "can have multiple originating elements coming from the same tpye"() { + def "does a full rebuild when too many originating elements are given for a resource"() { + when: + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt", type("Bar"), type("Baz")) + + then: + result.fullRebuildCause == "the generated resource 'foo.txt in SOURCE_OUTPUT' must have exactly one originating element, but had 2" + } + + def "can have multiple originating elements coming from the same type"() { when: filer.createSourceFile("Foo", methodInside("Bar"), methodInside("Bar")) + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt", methodInside("Bar"), methodInside("Bar")) then: !result.fullRebuildCause @@ -52,10 +72,13 @@ class IsolatingFilerTest extends IncrementalFilerTest { def "packages are valid originating elements"() { when: filer.createSourceFile("Foo", pkg("fizz")) + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt", pkg("fizz")) then: result.generatedTypesWithIsolatedOrigin.size() == 1 result.generatedTypesWithIsolatedOrigin["fizz.package-info"] == ["Foo"] as Set + result.generatedResourcesWithIsolatedOrigin.size() == 1 + result.generatedResourcesWithIsolatedOrigin["fizz.package-info"] == [sourceResource("foo.txt")] as Set } def "adds originating types to the processing result"() { @@ -63,10 +86,34 @@ class IsolatingFilerTest extends IncrementalFilerTest { filer.createSourceFile("Foo", pkg("pkg"), type("A"), methodInside("B")) filer.createSourceFile("Bar", type("B")) + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "foo.txt", pkg("pkg"), type("A"), methodInside("B")) + filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "bar.txt", type("B")) + then: result.generatedTypesWithIsolatedOrigin.size() == 3 result.generatedTypesWithIsolatedOrigin["A"] == ["Foo"] as Set result.generatedTypesWithIsolatedOrigin["pkg.package-info"] == ["Foo"] as Set result.generatedTypesWithIsolatedOrigin["B"] == ["Foo", "Bar"] as Set + + def foo = sourceResource("foo.txt") + def bar = sourceResource("bar.txt") + result.generatedResourcesWithIsolatedOrigin.size() == 3 + result.generatedResourcesWithIsolatedOrigin["A"] == [foo] as Set + result.generatedResourcesWithIsolatedOrigin["pkg.package-info"] == [foo] as Set + result.generatedResourcesWithIsolatedOrigin["B"] == [foo, bar] as Set + } + + def "handles resources in the three StandardLocation output locations"() { + when: + filer.createResource(inputLocation, "", "foo.txt", type("A")) + + then: + result.generatedResourcesWithIsolatedOrigin["A"] == [new GeneratedResource(resultLocation, "foo.txt")] as Set + + where: + inputLocation | resultLocation + StandardLocation.SOURCE_OUTPUT | GeneratedResource.Location.SOURCE_OUTPUT + StandardLocation.CLASS_OUTPUT | GeneratedResource.Location.CLASS_OUTPUT + StandardLocation.NATIVE_HEADER_OUTPUT | GeneratedResource.Location.NATIVE_HEADER_OUTPUT } } diff --git a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/AnnotationProcessorFixture.groovy b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/AnnotationProcessorFixture.groovy index c652d305a0b3b..6165cb67790b5 100644 --- a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/AnnotationProcessorFixture.groovy +++ b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/AnnotationProcessorFixture.groovy @@ -65,7 +65,9 @@ abstract class AnnotationProcessorFixture { import javax.lang.model.element.*; import javax.lang.model.util.*; import javax.tools.*; - + + import static javax.tools.StandardLocation.*; + @SupportedOptions({ "message" }) public class ${annotationName}Processor extends AbstractProcessor { private Map options; diff --git a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/HelperProcessorFixture.groovy b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/HelperProcessorFixture.groovy index e53cf0a50a03d..f0cdc4b7dac06 100644 --- a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/HelperProcessorFixture.groovy +++ b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/HelperProcessorFixture.groovy @@ -20,6 +20,8 @@ import groovy.transform.CompileStatic import org.gradle.api.internal.tasks.compile.incremental.processing.IncrementalAnnotationProcessorType import org.gradle.test.fixtures.file.TestFile +import javax.tools.StandardLocation + /** * Generates a "Helper" class for each annotated type. The helper has a "getValue()" method that returns * a greeting. The greeting is composed of a message and a suffix. The message is compiled into a support @@ -29,6 +31,8 @@ import org.gradle.test.fixtures.file.TestFile @CompileStatic class HelperProcessorFixture extends AnnotationProcessorFixture { String message = "greetings" + boolean writeResources + String resourceLocation = StandardLocation.CLASS_OUTPUT.toString() private String suffix = "" HelperProcessorFixture() { @@ -51,7 +55,7 @@ class HelperProcessorFixture extends AnnotationProcessorFixture { } String getGeneratorCode() { - """ + def baseCode = """ for (Element element : elements) { TypeElement typeElement = (TypeElement) element; String className = typeElement.getSimpleName().toString() + "Helper"; @@ -75,8 +79,28 @@ for (Element element : elements) { } catch (IOException e) { messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate source file " + className, element); } -} """ + def resourceCode = writeResources ? """ + try { + FileObject resourceFile = filer.createResource($resourceLocation, \"\", className + \"Resource.txt\", element); + Writer writer = resourceFile.openWriter(); + try { + String messageFromOptions = options.get("message"); + if (messageFromOptions == null) { + writer.write(HelperUtil.getValue() + "${suffix}"); + } else { + writer.write(messageFromOptions); + } + } finally { + writer.close(); + } + } catch (Exception e) { + messager.printMessage(Diagnostic.Kind.ERROR, "Failed to write resource file .txt"); + } +} +""" : "}" + + return baseCode + resourceCode } @Override diff --git a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/NonIncrementalProcessorFixture.groovy b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/NonIncrementalProcessorFixture.groovy index ba9e5893a6260..4c2ff69deb491 100644 --- a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/NonIncrementalProcessorFixture.groovy +++ b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/NonIncrementalProcessorFixture.groovy @@ -22,8 +22,6 @@ import groovy.transform.CompileStatic * An annotation processor which does all the things that we don't support for * incremental compilation: * - * - reading resources - * - writing resources * - generating files without originating elements * * Useful for testing error reporting. @@ -32,8 +30,6 @@ import groovy.transform.CompileStatic class NonIncrementalProcessorFixture extends AnnotationProcessorFixture { private boolean providesNoOriginatingElements - private boolean readsResources - private boolean writesResources NonIncrementalProcessorFixture() { super("Thing") @@ -44,16 +40,6 @@ class NonIncrementalProcessorFixture extends AnnotationProcessorFixture { this } - NonIncrementalProcessorFixture readingResources() { - readsResources = true - this - } - - NonIncrementalProcessorFixture writingResources() { - writesResources = true - this - } - String getGeneratorCode() { """ for (Element element : elements) { @@ -74,12 +60,6 @@ for (Element element : elements) { } catch (IOException e) { messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate source file " + className); } - try { - ${readsResources ? 'filer.getResource(StandardLocation.SOURCE_OUTPUT, "", "thing.txt");' : ""} - ${writesResources ? 'filer.createResource(StandardLocation.SOURCE_OUTPUT, "", "thing.txt");' : ""} - } catch (Exception e) { - messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate resource file thing.txt"); - } } """ } diff --git a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/ResourceGeneratingProcessorFixture.groovy b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/ResourceGeneratingProcessorFixture.groovy new file mode 100644 index 0000000000000..baaca5f2dfa91 --- /dev/null +++ b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/ResourceGeneratingProcessorFixture.groovy @@ -0,0 +1,75 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.language.fixtures + +import groovy.transform.CompileStatic + +import javax.tools.StandardLocation + +/** + * An annotation processor that exclusively generates resources. Like {@link NonIncrementalProcessorFixture}, can be configured to trigger error cases. + */ +@CompileStatic +class ResourceGeneratingProcessorFixture extends AnnotationProcessorFixture { + private boolean providesNoOriginatingElements + private List outputLocations = [StandardLocation.SOURCE_OUTPUT.toString()] + + ResourceGeneratingProcessorFixture() { + super("Thing") + } + + ResourceGeneratingProcessorFixture providingNoOriginatingElements() { + providesNoOriginatingElements = true + this + } + + ResourceGeneratingProcessorFixture withOutputLocations(String... locations) { + outputLocations = Arrays.asList(locations) + this + } + + ResourceGeneratingProcessorFixture withOutputLocations(List locations) { + outputLocations = locations + this + } + + String getGeneratorCode() { + def outputs = outputLocations.collect { """ + resourceFile = filer.createResource($it, \"\", resourceName${providesNoOriginatingElements ? '' : ', element'}); + writer = resourceFile.openWriter(); + try { + writer.write("We did it."); + } finally { + writer.close(); + } +""" }.join("\n ") + + """ +for (Element element : elements) { + TypeElement typeElement = (TypeElement) element; + String resourceName = typeElement.getSimpleName().toString() + ".txt"; + FileObject resourceFile; + Writer writer; + try { + $outputs + } catch (Exception e) { + messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate resource file " + resourceName + ": " + e.getMessage()); + } +} +""" + } +} diff --git a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/ServiceRegistryProcessorFixture.groovy b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/ServiceRegistryProcessorFixture.groovy index 82d0319ef7450..de6c9b575e6c2 100644 --- a/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/ServiceRegistryProcessorFixture.groovy +++ b/subprojects/language-java/src/testFixtures/groovy/org/gradle/language/fixtures/ServiceRegistryProcessorFixture.groovy @@ -19,6 +19,8 @@ package org.gradle.language.fixtures import groovy.transform.CompileStatic import org.gradle.api.internal.tasks.compile.incremental.processing.IncrementalAnnotationProcessorType +import javax.tools.StandardLocation + /** * A processor that collects all types annotated with the @Service annotation * and generates a single ServiceRegistry class from them. The registry has @@ -27,6 +29,8 @@ import org.gradle.api.internal.tasks.compile.incremental.processing.IncrementalA */ @CompileStatic class ServiceRegistryProcessorFixture extends AnnotationProcessorFixture { + boolean writeResources + String resourceLocation = StandardLocation.CLASS_OUTPUT.toString() ServiceRegistryProcessorFixture() { super("Service") @@ -34,7 +38,7 @@ class ServiceRegistryProcessorFixture extends AnnotationProcessorFixture { } String getGeneratorCode() { - """ + def baseCode = """ String className = "ServiceRegistry"; try { JavaFileObject sourceFile = filer.createSourceFile(className, elements.toArray(new Element[0])); @@ -56,5 +60,25 @@ try { messager.printMessage(Diagnostic.Kind.ERROR, "Failed to generate source file " + className); } """ + def resourceCode = writeResources ? """ + try { + FileObject resourceFile = filer.createResource($resourceLocation, \"\", className + \"Resource.txt\", elements.toArray(new Element[0])); + Writer writer = resourceFile.openWriter(); + try { + for (Element element : elements) { + TypeElement typeElement = (TypeElement) element; + String name = typeElement.getQualifiedName().toString(); + writer.write(name); + writer.write('\\n'); + } + } finally { + writer.close(); + } + } catch (Exception e) { + messager.printMessage(Diagnostic.Kind.ERROR, "Failed to write resource file .txt"); + } +""" : "" + + return baseCode + resourceCode } } diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppIncrementalBuildStaleOutputsIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppIncrementalBuildStaleOutputsIntegrationTest.groovy index 2de4f7c4cc3ee..520c7a45986cf 100644 --- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppIncrementalBuildStaleOutputsIntegrationTest.groovy +++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppIncrementalBuildStaleOutputsIntegrationTest.groovy @@ -122,7 +122,7 @@ class CppIncrementalBuildStaleOutputsIntegrationTest extends AbstractInstalledTo executable("app/build/exe/main/debug/app").assertDoesNotExist() file("app/build/exe/main/debug").assertDoesNotExist() - file("app/build/obj/main/debug").assertHasDescendants() + file("app/build/obj/main/debug").assertDoesNotExist() installation("app/build/install/main/debug").assertNotInstalled() sharedLibrary("greeter/build/lib/main/debug/greeter").assertExists() @@ -159,7 +159,7 @@ class CppIncrementalBuildStaleOutputsIntegrationTest extends AbstractInstalledTo executable("build/exe/main/debug/app").assertDoesNotExist() file("build/exe/main/debug").assertDoesNotExist() - file("build/obj/main/debug").assertHasDescendants() + file("build/obj/main/debug").assertDoesNotExist() installation("build/install/main/debug").assertNotInstalled() } @@ -192,7 +192,7 @@ class CppIncrementalBuildStaleOutputsIntegrationTest extends AbstractInstalledTo sharedLibrary("build/lib/main/debug/hello").assertDoesNotExist() file("build/lib/main/debug").assertDoesNotExist() - file("build/obj/main/debug").assertHasDescendants() + file("build/obj/main/debug").assertDoesNotExist() } private List expectIntermediateDescendants(SourceElement sourceElement) { diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppPreCompiledHeaderSourcesIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppPreCompiledHeaderSourcesIntegrationTest.groovy index 7eb5fcba091e1..3cebc7aba7adc 100644 --- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppPreCompiledHeaderSourcesIntegrationTest.groovy +++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/cpp/CppPreCompiledHeaderSourcesIntegrationTest.groovy @@ -40,8 +40,10 @@ class CppPreCompiledHeaderSourcesIntegrationTest extends AbstractNativePreCompil then: libAndPCHTasksExecuted() pchCompiledOnceForEach([ PCHHeaderDirName ]) - output.contains "Caching disabled for task ':compileHelloSharedLibraryCppPreCompiledHeader': Caching has not been enabled for the task" - output.contains "Caching disabled for task ':compileHelloSharedLibraryHelloCpp': 'Pre-compiled headers are used' satisfied" + output.contains "Caching disabled for task ':compileHelloSharedLibraryCppPreCompiledHeader' because:\n" + + " Caching has not been enabled for the task" + output.contains "Caching disabled for task ':compileHelloSharedLibraryHelloCpp' because:\n" + + " 'Pre-compiled headers are used' satisfied" } @Requires(TestPrecondition.MAC_OS_X) diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/AbstractSwiftIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/AbstractSwiftIntegrationTest.groovy index 89bd76e3b8e69..6b5dc81aae060 100644 --- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/AbstractSwiftIntegrationTest.groovy +++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/AbstractSwiftIntegrationTest.groovy @@ -21,6 +21,7 @@ import org.gradle.nativeplatform.fixtures.ToolChainRequirement import org.gradle.nativeplatform.fixtures.app.SourceElement import org.gradle.nativeplatform.fixtures.app.Swift3 import org.gradle.nativeplatform.fixtures.app.Swift4 +import org.gradle.nativeplatform.fixtures.app.Swift5 import org.gradle.util.Matchers @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC) @@ -121,6 +122,11 @@ abstract class AbstractSwiftIntegrationTest extends AbstractSwiftComponentIntegr return new Swift4('project') } + @Override + SourceElement getSwift5Component() { + return new Swift5('project') + } + @Override String getTaskNameToAssembleDevelopmentBinary() { return "assemble" diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/SwiftIncrementalBuildIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/SwiftIncrementalBuildIntegrationTest.groovy index 6a011931b733d..a185d20a5e187 100644 --- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/SwiftIncrementalBuildIntegrationTest.groovy +++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/SwiftIncrementalBuildIntegrationTest.groovy @@ -32,6 +32,7 @@ import org.gradle.nativeplatform.fixtures.app.SourceElement import org.gradle.nativeplatform.fixtures.app.SwiftApp import org.gradle.nativeplatform.fixtures.app.SwiftLib import org.gradle.test.fixtures.file.TestFile +import org.gradle.util.VersionNumber @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC) class SwiftIncrementalBuildIntegrationTest extends AbstractInstalledToolChainIntegrationSpec { @@ -140,7 +141,14 @@ class SwiftIncrementalBuildIntegrationTest extends AbstractInstalledToolChainInt result.assertTasksNotSkipped(assembleAppTasks) outputs.deletedClasses("multiply", "sum") - outputs.recompiledClasses('greeter', 'renamed-sum', 'main') + + // See https://github.com/gradle/gradle-native/issues/1004 + if (toolchainUnderTest.version.major < 5) { + outputs.recompiledClasses('greeter', 'renamed-sum', 'main') + } else { + outputs.recompiledClasses('renamed-sum') + } + outputDirectory.assertContainsDescendants(expectedIntermediateDescendants(app.alternate)) installation("build/install/main/debug").exec().out == app.expectedAlternateOutput } @@ -168,7 +176,14 @@ class SwiftIncrementalBuildIntegrationTest extends AbstractInstalledToolChainInt result.assertTasksExecuted(assembleLibTasks) result.assertTasksNotSkipped(assembleLibTasks) outputs.deletedClasses("multiply", "sum") - outputs.recompiledClasses('greeter', 'renamed-sum') + + // See https://github.com/gradle/gradle-native/issues/1004 + if (toolchainUnderTest.version.major < 5) { + outputs.recompiledClasses('greeter', 'renamed-sum') + } else { + outputs.recompiledClasses('renamed-sum') + } + outputDirectory.assertContainsDescendants(expectedIntermediateDescendants(lib.alternate)) sharedLibrary("build/lib/main/debug/Hello").assertExists() } @@ -348,6 +363,9 @@ class SwiftIncrementalBuildIntegrationTest extends AbstractInstalledToolChainInt result.add(dependFileFor(swiftFile).relativizeFrom(intermediateFilesDir).path) result.add(swiftDepsFileFor(swiftFile).relativizeFrom(intermediateFilesDir).path) } + if (toolChain.version.compareTo(VersionNumber.parse("4.2")) >= 0) { + result.add("module.swiftdeps~moduleonly") + } result.add("module.swiftdeps") result.add("output-file-map.json") return result diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/SwiftIncrementalCompileIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/SwiftIncrementalCompileIntegrationTest.groovy index 6154d16e982ca..3b59572bf7c32 100644 --- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/SwiftIncrementalCompileIntegrationTest.groovy +++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/SwiftIncrementalCompileIntegrationTest.groovy @@ -109,6 +109,7 @@ class SwiftIncrementalCompileIntegrationTest extends AbstractInstalledToolChainI failure.assertHasErrorOutput("error: invalid redeclaration of 'sum(a:b:)'") } + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_4_OR_OLDER) def 'removing a file rebuilds everything'() { given: def outputs = new CompilationOutputsFixture(file("build/obj/main/debug"), [ ".o" ]) @@ -128,6 +129,26 @@ class SwiftIncrementalCompileIntegrationTest extends AbstractInstalledToolChainI outputs.deletedClasses("multiply") } + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_5) + def 'removing an isolated file does not rebuild anything'() { + given: + def outputs = new CompilationOutputsFixture(file("build/obj/main/debug"), [ ".o" ]) + def app = new SwiftApp() + settingsFile << "rootProject.name = 'app'" + app.writeToProject(testDirectory) + buildFile << """ + apply plugin: 'swift-application' + """ + + outputs.snapshot { succeeds("compileDebugSwift") } + file("src/main/swift/multiply.swift").delete() + + expect: + succeeds("compileDebugSwift") + outputs.recompiledClasses() + outputs.deletedClasses("multiply") + } + def 'changing compiler arguments rebuilds everything'() { given: def outputs = new CompilationOutputsFixture(file("build/obj/main/debug"), [ ".o" ]) diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/tasks/UnexportMainSymbolIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/tasks/UnexportMainSymbolIntegrationTest.groovy index 5583ecce694a9..9e985801d5b4b 100644 --- a/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/tasks/UnexportMainSymbolIntegrationTest.groovy +++ b/subprojects/language-native/src/integTest/groovy/org/gradle/language/swift/tasks/UnexportMainSymbolIntegrationTest.groovy @@ -98,6 +98,48 @@ class UnexportMainSymbolIntegrationTest extends AbstractInstalledToolChainIntegr file("build/relocated").assertHasDescendants("main.o", "other.o") } + def "relocate _main symbol works incrementally"() { + writeMainSwift() + + when: + succeeds("unexport") + then: + assertMainSymbolIsNotExported("build/relocated/main.o") + file("build/relocated").assertHasDescendants("main.o") + + when: + def mainObject = file("build/relocated/main.o") + mainObject.makeOlder() + def oldTimestamp = mainObject.lastModified() + def otherFile = file("src/main/swift/other.swift") + otherFile << """ + class Other {} + """ + succeeds("unexport") + then: + assertMainSymbolIsNotExported("build/relocated/main.o") + file("build/relocated").assertHasDescendants("main.o", "other.o") + mainObject.lastModified() == oldTimestamp + + when: + assert otherFile.delete() + succeeds("unexport") + then: + assertMainSymbolIsNotExported("build/relocated/main.o") + file("build/relocated").assertHasDescendants("main.o") + mainObject.lastModified() == oldTimestamp + + when: + otherFile << """ + class Other {} + """ + succeeds("unexport") + assert file("build/relocated/other.o").delete() + succeeds("unexport") + then: + mainObject.lastModified() > oldTimestamp + } + def "can relocate when there is no main symbol"() { file("src/main/swift/notMain.swift") << """ class NotMain {} diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/swiftpm/SwiftPackageManagerExportIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/swiftpm/SwiftPackageManagerExportIntegrationTest.groovy index bf59eec5dffff..579f9151bf5bd 100644 --- a/subprojects/language-native/src/integTest/groovy/org/gradle/swiftpm/SwiftPackageManagerExportIntegrationTest.groovy +++ b/subprojects/language-native/src/integTest/groovy/org/gradle/swiftpm/SwiftPackageManagerExportIntegrationTest.groovy @@ -16,8 +16,14 @@ package org.gradle.swiftpm + +import spock.lang.Ignore + class SwiftPackageManagerExportIntegrationTest extends AbstractSwiftPackageManagerExportIntegrationTest { + // Swift Package Manager returns an error code when no target are available: + // See https://github.com/gradle/gradle-native/issues/1006 + @Ignore def "produces manifest for build with no native components"() { given: settingsFile << "include 'lib1', 'lib2'" diff --git a/subprojects/language-native/src/integTest/groovy/org/gradle/swiftpm/SwiftPackageManagerSwiftBuildExportIntegrationTest.groovy b/subprojects/language-native/src/integTest/groovy/org/gradle/swiftpm/SwiftPackageManagerSwiftBuildExportIntegrationTest.groovy index 716cbc3ed2e1a..c68a939d1334f 100644 --- a/subprojects/language-native/src/integTest/groovy/org/gradle/swiftpm/SwiftPackageManagerSwiftBuildExportIntegrationTest.groovy +++ b/subprojects/language-native/src/integTest/groovy/org/gradle/swiftpm/SwiftPackageManagerSwiftBuildExportIntegrationTest.groovy @@ -16,6 +16,8 @@ package org.gradle.swiftpm +import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain +import org.gradle.nativeplatform.fixtures.ToolChainRequirement import org.gradle.nativeplatform.fixtures.app.SwiftAppWithLibraries import org.gradle.nativeplatform.fixtures.app.SwiftLib @@ -187,6 +189,8 @@ let package = Package( swiftPmBuildSucceeds() } + // See https://github.com/gradle/gradle-native/issues/1007 + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_4_OR_OLDER) def "produces manifest for Swift component with declared Swift language version"() { given: buildFile << """ diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalNativeCompiler.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalNativeCompiler.java index 7b8dac01b3e2c..964de8b91d322 100644 --- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalNativeCompiler.java +++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/incremental/IncrementalNativeCompiler.java @@ -19,9 +19,6 @@ import com.google.common.collect.Lists; import org.gradle.api.NonNullApi; import org.gradle.api.internal.TaskOutputsInternal; -import org.gradle.api.logging.LogLevel; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; import org.gradle.api.tasks.WorkResult; import org.gradle.api.tasks.WorkResults; import org.gradle.cache.PersistentStateCache; @@ -29,6 +26,8 @@ import org.gradle.language.base.internal.compile.Compiler; import org.gradle.language.base.internal.tasks.SimpleStaleClassCleaner; import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.util.Collections; @@ -36,7 +35,7 @@ @NonNullApi public class IncrementalNativeCompiler implements Compiler { - private final Logger logger = Logging.getLogger(IncrementalNativeCompiler.class); + private final Logger logger = LoggerFactory.getLogger(IncrementalNativeCompiler.class); private final Compiler delegateCompiler; private final TaskOutputsInternal outputs; @@ -87,7 +86,7 @@ private List getSourceFilesForPch(T spec) { } else { boolean containsHeader = headers.contains(header); if (containsHeader) { - logger.log(LogLevel.WARN, getCantUsePCHMessage(spec.getPreCompiledHeader(), sourceFile)); + logger.warn(getCantUsePCHMessage(spec.getPreCompiledHeader(), sourceFile)); } } } diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/toolchains/DefaultToolChainSelector.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/toolchains/DefaultToolChainSelector.java index b8925e6e54b7f..fca940989b841 100644 --- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/toolchains/DefaultToolChainSelector.java +++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/internal/toolchains/DefaultToolChainSelector.java @@ -105,13 +105,12 @@ private NativeToolChainInternal getToolChain(NativeLanguage sourceLanguage, Nati } static SwiftVersion toSwiftVersion(VersionNumber swiftCompilerVersion) { - if (swiftCompilerVersion.getMajor() == 3) { - return SwiftVersion.SWIFT3; - } else if (swiftCompilerVersion.getMajor() == 4) { - return SwiftVersion.SWIFT4; - } else { - throw new IllegalArgumentException(String.format("Swift language version is unknown for the specified Swift compiler version (%s)", swiftCompilerVersion.toString())); + for (SwiftVersion version : SwiftVersion.values()) { + if (version.getVersion() == swiftCompilerVersion.getMajor()) { + return version; + } } + throw new IllegalArgumentException(String.format("Swift language version is unknown for the specified Swift compiler version (%s)", swiftCompilerVersion.toString())); } class DefaultResult implements Result { diff --git a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeCompileTask.java b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeCompileTask.java index 1402efe2c49c4..c09cf79a44cc4 100644 --- a/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeCompileTask.java +++ b/subprojects/language-native/src/main/java/org/gradle/language/nativeplatform/tasks/AbstractNativeCompileTask.java @@ -33,9 +33,9 @@ import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.WorkResult; -import org.gradle.api.tasks.incremental.IncrementalTaskInputs; import org.gradle.internal.Cast; import org.gradle.internal.operations.logging.BuildOperationLogger; import org.gradle.internal.operations.logging.BuildOperationLoggerFactory; @@ -50,6 +50,8 @@ import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec; import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal; import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider; +import org.gradle.work.Incremental; +import org.gradle.work.InputChanges; import javax.inject.Inject; import java.util.LinkedHashMap; @@ -114,7 +116,7 @@ protected FileCollectionFactory getFileCollectionFactory() { } @TaskAction - public void compile(IncrementalTaskInputs inputs) { + public void compile(InputChanges inputs) { BuildOperationLogger operationLogger = getOperationLoggerFactory().newOperationLogger(getName(), getTemporaryDir()); NativeCompileSpec spec = createCompileSpec(); spec.setTargetPlatform(targetPlatform.get()); @@ -260,6 +262,7 @@ public ConfigurableFileCollection getSystemIncludes() { /** * Returns the source files to be compiled. */ + @SkipWhenEmpty @InputFiles @PathSensitive(PathSensitivity.RELATIVE) public ConfigurableFileCollection getSource() { @@ -301,6 +304,7 @@ public ListProperty getCompilerArgs() { * * @since 4.3 */ + @Incremental @InputFiles @PathSensitive(PathSensitivity.NAME_ONLY) protected FileCollection getHeaderDependencies() { diff --git a/subprojects/language-native/src/main/java/org/gradle/language/plugins/NativeBasePlugin.java b/subprojects/language-native/src/main/java/org/gradle/language/plugins/NativeBasePlugin.java index 11fe1facecb5e..26090186a8b20 100644 --- a/subprojects/language-native/src/main/java/org/gradle/language/plugins/NativeBasePlugin.java +++ b/subprojects/language-native/src/main/java/org/gradle/language/plugins/NativeBasePlugin.java @@ -439,7 +439,7 @@ private TaskProvider extractSymbols(final TaskProvider { + dependencyHandler.registerTransform(UnzipTransform.class, variantTransform -> { variantTransform.getFrom().attribute(ArtifactAttributes.ARTIFACT_FORMAT, ZIP_TYPE); variantTransform.getFrom().attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.class, Usage.C_PLUS_PLUS_API)); variantTransform.getTo().attribute(ArtifactAttributes.ARTIFACT_FORMAT, DIRECTORY_TYPE); diff --git a/subprojects/language-native/src/main/java/org/gradle/language/rc/tasks/WindowsResourceCompile.java b/subprojects/language-native/src/main/java/org/gradle/language/rc/tasks/WindowsResourceCompile.java index 4e56afed1497c..be061688d45df 100644 --- a/subprojects/language-native/src/main/java/org/gradle/language/rc/tasks/WindowsResourceCompile.java +++ b/subprojects/language-native/src/main/java/org/gradle/language/rc/tasks/WindowsResourceCompile.java @@ -30,9 +30,9 @@ import org.gradle.api.tasks.OutputDirectory; import org.gradle.api.tasks.PathSensitive; import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.WorkResult; -import org.gradle.api.tasks.incremental.IncrementalTaskInputs; import org.gradle.internal.Cast; import org.gradle.internal.operations.logging.BuildOperationLogger; import org.gradle.internal.operations.logging.BuildOperationLoggerFactory; @@ -47,6 +47,8 @@ import org.gradle.nativeplatform.toolchain.internal.NativeCompileSpec; import org.gradle.nativeplatform.toolchain.internal.NativeToolChainInternal; import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider; +import org.gradle.work.Incremental; +import org.gradle.work.InputChanges; import javax.inject.Inject; import java.io.File; @@ -79,7 +81,7 @@ public WindowsResourceCompile() { incrementalCompiler = getIncrementalCompilerBuilder().newCompiler(this, source, includes, macros, Providers.FALSE); getInputs().property("outputType", new Callable() { @Override - public String call() throws Exception { + public String call() { NativeToolChainInternal nativeToolChain = (NativeToolChainInternal) toolChain.get(); NativePlatformInternal nativePlatform = (NativePlatformInternal) targetPlatform.get(); return NativeToolChainInternal.Identifier.identify(nativeToolChain, nativePlatform); @@ -98,7 +100,7 @@ public BuildOperationLoggerFactory getOperationLoggerFactory() { } @TaskAction - public void compile(IncrementalTaskInputs inputs) { + public void compile(InputChanges inputs) { BuildOperationLogger operationLogger = getOperationLoggerFactory().newOperationLogger(getName(), getTemporaryDir()); NativeCompileSpec spec = new DefaultWindowsResourceCompileSpec(); @@ -161,6 +163,7 @@ public void setOutputDir(File outputDir) { /** * Returns the header directories to be used for compilation. */ + @Incremental @PathSensitive(PathSensitivity.RELATIVE) @InputFiles public ConfigurableFileCollection getIncludes() { @@ -177,6 +180,7 @@ public void includes(Object includeRoots) { /** * Returns the source files to be compiled. */ + @SkipWhenEmpty @PathSensitive(PathSensitivity.RELATIVE) @InputFiles public ConfigurableFileCollection getSource() { @@ -218,6 +222,7 @@ public ListProperty getCompilerArgs() { * * @since 4.5 */ + @Incremental @InputFiles @PathSensitive(PathSensitivity.NAME_ONLY) protected FileCollection getHeaderDependencies() { diff --git a/subprojects/language-native/src/main/java/org/gradle/language/swift/tasks/SwiftCompile.java b/subprojects/language-native/src/main/java/org/gradle/language/swift/tasks/SwiftCompile.java index a386b037e9e12..1238a96dd1ab1 100644 --- a/subprojects/language-native/src/main/java/org/gradle/language/swift/tasks/SwiftCompile.java +++ b/subprojects/language-native/src/main/java/org/gradle/language/swift/tasks/SwiftCompile.java @@ -18,7 +18,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import org.gradle.api.Action; import org.gradle.api.DefaultTask; import org.gradle.api.Incubating; import org.gradle.api.file.ConfigurableFileCollection; @@ -40,8 +39,6 @@ import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.WorkResult; -import org.gradle.api.tasks.incremental.IncrementalTaskInputs; -import org.gradle.api.tasks.incremental.InputFileDetails; import org.gradle.internal.Cast; import org.gradle.internal.operations.logging.BuildOperationLogger; import org.gradle.internal.operations.logging.BuildOperationLoggerFactory; @@ -59,6 +56,9 @@ import org.gradle.nativeplatform.toolchain.internal.PlatformToolProvider; import org.gradle.nativeplatform.toolchain.internal.compilespec.SwiftCompileSpec; import org.gradle.nativeplatform.toolchain.internal.swift.IncrementalSwiftCompiler; +import org.gradle.work.ChangeType; +import org.gradle.work.FileChange; +import org.gradle.work.InputChanges; import javax.inject.Inject; import java.io.File; @@ -272,36 +272,22 @@ private Compiler createCompiler() { } @TaskAction - void compile(IncrementalTaskInputs inputs) { + void compile(InputChanges inputs) { final List removedFiles = Lists.newArrayList(); final Set changedFiles = Sets.newHashSet(); boolean isIncremental = inputs.isIncremental(); // TODO: This should become smarter and move into the compiler infrastructure instead - // of the task, similar to how the other native languages are done. - // For now, this does a rudimentary incremental build analysis by looking at - // which files changed and marking the compilation incremental or not. + // of the task, similar to how the other native languages are done. + // For now, this does a rudimentary incremental build analysis by looking at + // which files changed . if (isIncremental) { - inputs.outOfDate(new Action() { - @Override - public void execute(InputFileDetails inputFileDetails) { - if (inputFileDetails.isModified()) { - changedFiles.add(inputFileDetails.getFile()); - } + for (FileChange fileChange : inputs.getFileChanges(getSource())) { + if (fileChange.getChangeType() == ChangeType.REMOVED) { + removedFiles.add(fileChange.getFile()); + } else { + changedFiles.add(fileChange.getFile()); } - }); - inputs.removed(new Action() { - @Override - public void execute(InputFileDetails removed) { - removedFiles.add(removed.getFile()); - } - }); - - Set allSourceFiles = getSource().getFiles(); - if (!allSourceFiles.containsAll(changedFiles)) { - // If a non-source file changed, the compilation cannot be incremental - // due to the way the Swift compiler detects changes from other modules - isIncremental = false; } } diff --git a/subprojects/language-native/src/main/java/org/gradle/language/swift/tasks/UnexportMainSymbol.java b/subprojects/language-native/src/main/java/org/gradle/language/swift/tasks/UnexportMainSymbol.java index 96405543578ab..7809fdbafd331 100644 --- a/subprojects/language-native/src/main/java/org/gradle/language/swift/tasks/UnexportMainSymbol.java +++ b/subprojects/language-native/src/main/java/org/gradle/language/swift/tasks/UnexportMainSymbol.java @@ -30,10 +30,14 @@ import org.gradle.api.tasks.PathSensitivity; import org.gradle.api.tasks.SkipWhenEmpty; import org.gradle.api.tasks.TaskAction; -import org.gradle.internal.os.OperatingSystem; import org.gradle.internal.UncheckedException; +import org.gradle.internal.os.OperatingSystem; import org.gradle.language.swift.tasks.internal.SymbolHider; import org.gradle.process.ExecSpec; +import org.gradle.util.GFileUtils; +import org.gradle.work.ChangeType; +import org.gradle.work.FileChange; +import org.gradle.work.InputChanges; import java.io.File; import java.io.IOException; @@ -82,40 +86,58 @@ public DirectoryProperty getOutputDirectory() { } @TaskAction - public void unexport() { - for (final File file: source) { - final File relocatedObject = outputDirectory.file(file.getName()).get().getAsFile(); - if (OperatingSystem.current().isWindows()) { - try { - final SymbolHider symbolHider = new SymbolHider(file); - symbolHider.hideSymbol("main"); // 64 bit - symbolHider.hideSymbol("_main"); // 32 bit - symbolHider.saveTo(relocatedObject); - } catch (IOException e) { - throw UncheckedException.throwAsUncheckedException(e); - } + public void unexport(InputChanges inputChanges) { + if (!inputChanges.isIncremental()) { + GFileUtils.cleanDirectory(outputDirectory.get().getAsFile()); + } + for (FileChange change : inputChanges.getFileChanges(getObjects())) { + if (change.getChangeType() == ChangeType.REMOVED) { + File relocatedFileLocation = relocatedObject(change.getFile()); + relocatedFileLocation.delete(); } else { - getProject().exec(new Action() { - @Override - public void execute(ExecSpec execSpec) { - // TODO: should use target platform to make this decision - if (OperatingSystem.current().isMacOsX()) { - execSpec.executable("ld"); // TODO: Locate this tool from a tool provider - execSpec.args(file); - execSpec.args("-o", relocatedObject); - execSpec.args("-r"); // relink, produce another object file - execSpec.args("-unexported_symbol", "_main"); // hide _main symbol - } else if (OperatingSystem.current().isLinux()) { - execSpec.executable("objcopy"); // TODO: Locate this tool from a tool provider - execSpec.args("-L", "main"); // hide main symbol - execSpec.args(file); - execSpec.args(relocatedObject); - } else { - throw new IllegalStateException("Do not know how to unexport a main symbol on " + OperatingSystem.current()); - } - } - }); + if (change.getFile().isFile()) { + unexportMainSymbol(change.getFile()); + } } } } + + private void unexportMainSymbol(File object) { + final File relocatedObject = relocatedObject(object); + if (OperatingSystem.current().isWindows()) { + try { + final SymbolHider symbolHider = new SymbolHider(object); + symbolHider.hideSymbol("main"); // 64 bit + symbolHider.hideSymbol("_main"); // 32 bit + symbolHider.saveTo(relocatedObject); + } catch (IOException e) { + throw UncheckedException.throwAsUncheckedException(e); + } + } else { + getProject().exec(new Action() { + @Override + public void execute(ExecSpec execSpec) { + // TODO: should use target platform to make this decision + if (OperatingSystem.current().isMacOsX()) { + execSpec.executable("ld"); // TODO: Locate this tool from a tool provider + execSpec.args(object); + execSpec.args("-o", relocatedObject); + execSpec.args("-r"); // relink, produce another object file + execSpec.args("-unexported_symbol", "_main"); // hide _main symbol + } else if (OperatingSystem.current().isLinux()) { + execSpec.executable("objcopy"); // TODO: Locate this tool from a tool provider + execSpec.args("-L", "main"); // hide main symbol + execSpec.args(object); + execSpec.args(relocatedObject); + } else { + throw new IllegalStateException("Do not know how to unexport a main symbol on " + OperatingSystem.current()); + } + } + }); + } + } + + private File relocatedObject(File object) { + return outputDirectory.file(object.getName()).get().getAsFile(); + } } diff --git a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/toolchains/DefaultToolChainSelectorTest.groovy b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/toolchains/DefaultToolChainSelectorTest.groovy index 0ebfb5d37f495..7a3f2206717a0 100644 --- a/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/toolchains/DefaultToolChainSelectorTest.groovy +++ b/subprojects/language-native/src/test/groovy/org/gradle/language/nativeplatform/internal/toolchains/DefaultToolChainSelectorTest.groovy @@ -36,6 +36,7 @@ import spock.lang.Unroll import static org.gradle.language.swift.SwiftVersion.SWIFT3 import static org.gradle.language.swift.SwiftVersion.SWIFT4 +import static org.gradle.language.swift.SwiftVersion.SWIFT5 @UsesNativeServices class DefaultToolChainSelectorTest extends Specification { @@ -102,6 +103,10 @@ class DefaultToolChainSelectorTest extends Specification { where: // See https://swift.org/download compilerVersion | languageVersion + '5.0.0' | SWIFT5 + '4.2.3' | SWIFT4 + '4.1.3' | SWIFT4 + '4.1' | SWIFT4 '4.0.3' | SWIFT4 '4.0.2' | SWIFT4 '4.0' | SWIFT4 diff --git a/subprojects/language-native/src/testFixtures/groovy/org/gradle/language/swift/AbstractSwiftComponentIntegrationTest.groovy b/subprojects/language-native/src/testFixtures/groovy/org/gradle/language/swift/AbstractSwiftComponentIntegrationTest.groovy index 4a9939b42c59a..be1ea8d25d2fd 100644 --- a/subprojects/language-native/src/testFixtures/groovy/org/gradle/language/swift/AbstractSwiftComponentIntegrationTest.groovy +++ b/subprojects/language-native/src/testFixtures/groovy/org/gradle/language/swift/AbstractSwiftComponentIntegrationTest.groovy @@ -95,6 +95,34 @@ abstract class AbstractSwiftComponentIntegrationTest extends AbstractNativeLangu result.assertTasksExecuted(tasksToAssembleDevelopmentBinaryOfComponentUnderTest, ":$taskNameToAssembleDevelopmentBinary") } + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_5) + def "can build Swift 4 source code on Swift 5 compiler"() { + given: + makeSingleProject() + swift4Component.writeToProject(testDirectory) + buildFile << """ + ${componentUnderTestDsl} { + sourceCompatibility = SwiftVersion.SWIFT4 + } + + task verifyBinariesSwiftVersion { + doLast { + ${componentUnderTestDsl}.binaries.get().each { + assert it.targetPlatform.sourceCompatibility == SwiftVersion.SWIFT4 + } + } + } + """ + settingsFile << "rootProject.name = '${swift4Component.projectName}'" + + when: + succeeds "verifyBinariesSwiftVersion" + succeeds taskNameToAssembleDevelopmentBinary + + then: + result.assertTasksExecuted(tasksToAssembleDevelopmentBinaryOfComponentUnderTest, ":$taskNameToAssembleDevelopmentBinary") + } + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_3) def "throws exception with meaningful message when building Swift 4 source code on Swift 3 compiler"() { given: @@ -124,6 +152,93 @@ abstract class AbstractSwiftComponentIntegrationTest extends AbstractNativeLangu failure.assertHasCause("Swift compiler version '${toolChain.version}' doesn't support Swift language version '${SwiftVersion.SWIFT4.version}'") } + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_3) + def "throws exception with meaningful message when building Swift 5 source code on Swift 3 compiler"() { + given: + makeSingleProject() + swift5Component.writeToProject(testDirectory) + buildFile << """ + ${componentUnderTestDsl} { + sourceCompatibility = SwiftVersion.SWIFT5 + } + + task verifyBinariesSwiftVersion { + doLast { + ${componentUnderTestDsl}.binaries.get().each { + assert it.targetPlatform.sourceCompatibility == SwiftVersion.SWIFT5 + } + } + } + """ + settingsFile << "rootProject.name = '${swift5Component.projectName}'" + + when: + succeeds "verifyBinariesSwiftVersion" + fails taskNameToAssembleDevelopmentBinary + + then: + failure.assertHasDescription("Execution failed for task '$developmentBinaryCompileTask'.") + failure.assertHasCause("Swift compiler version '${toolChain.version}' doesn't support Swift language version '${SwiftVersion.SWIFT5.version}'") + } + + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_4) + def "throws exception with meaningful message when building Swift 5 source code on Swift 4 compiler"() { + given: + makeSingleProject() + swift5Component.writeToProject(testDirectory) + buildFile << """ + ${componentUnderTestDsl} { + sourceCompatibility = SwiftVersion.SWIFT5 + } + + task verifyBinariesSwiftVersion { + doLast { + ${componentUnderTestDsl}.binaries.get().each { + assert it.targetPlatform.sourceCompatibility == SwiftVersion.SWIFT5 + } + } + } + """ + settingsFile << "rootProject.name = '${swift5Component.projectName}'" + + when: + succeeds "verifyBinariesSwiftVersion" + fails taskNameToAssembleDevelopmentBinary + + then: + failure.assertHasDescription("Execution failed for task '$developmentBinaryCompileTask'.") + failure.assertHasCause("Swift compiler version '${toolChain.version}' doesn't support Swift language version '${SwiftVersion.SWIFT5.version}'") + } + + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_5) + def "throws exception with meaningful message when building Swift 3 source code on Swift 5 compiler"() { + given: + makeSingleProject() + swift3Component.writeToProject(testDirectory) + buildFile << """ + ${componentUnderTestDsl} { + sourceCompatibility = SwiftVersion.SWIFT3 + } + + task verifyBinariesSwiftVersion { + doLast { + ${componentUnderTestDsl}.binaries.get().each { + assert it.targetPlatform.sourceCompatibility == SwiftVersion.SWIFT3 + } + } + } + """ + settingsFile << "rootProject.name = '${swift3Component.projectName}'" + + when: + succeeds "verifyBinariesSwiftVersion" + fails taskNameToAssembleDevelopmentBinary + + then: + failure.assertHasDescription("Execution failed for task '$developmentBinaryCompileTask'.") + failure.assertHasCause("Swift compiler version '${toolChain.version}' doesn't support Swift language version '${SwiftVersion.SWIFT3.version}'") + } + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_3) def "can compile Swift 3 component on Swift 3 compiler"() { given: @@ -172,10 +287,34 @@ abstract class AbstractSwiftComponentIntegrationTest extends AbstractNativeLangu result.assertTasksExecuted(tasksToAssembleDevelopmentBinaryOfComponentUnderTest, ":$taskNameToAssembleDevelopmentBinary") } + @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_5) + def "can compile Swift 5 component on Swift 5 compiler"() { + given: + makeSingleProject() + swift5Component.writeToProject(testDirectory) + buildFile << """ + task verifyBinariesSwiftVersion { + doLast { + ${componentUnderTestDsl}.binaries.get().each { + assert it.targetPlatform.sourceCompatibility == SwiftVersion.SWIFT5 + } + } + } + """ + settingsFile << "rootProject.name = '${swift5Component.projectName}'" + + when: + succeeds "verifyBinariesSwiftVersion" + succeeds taskNameToAssembleDevelopmentBinary + + then: + result.assertTasksExecuted(tasksToAssembleDevelopmentBinaryOfComponentUnderTest, ":$taskNameToAssembleDevelopmentBinary") + } + def "assemble task warns when current operating system family is excluded"() { given: makeSingleProject() - swift4Component.writeToProject(testDirectory) + componentUnderTest.writeToProject(testDirectory) and: buildFile << configureTargetMachines("machines.os('some-other-family')") @@ -190,7 +329,7 @@ abstract class AbstractSwiftComponentIntegrationTest extends AbstractNativeLangu def "build task warns when current operating system family is excluded"() { given: makeSingleProject() - swift4Component.writeToProject(testDirectory) + componentUnderTest.writeToProject(testDirectory) and: buildFile << configureTargetMachines("machines.os('some-other-family')") @@ -205,7 +344,7 @@ abstract class AbstractSwiftComponentIntegrationTest extends AbstractNativeLangu def "does not fail when current operating system family is excluded but assemble is not invoked"() { given: makeSingleProject() - swift4Component.writeToProject(testDirectory) + componentUnderTest.writeToProject(testDirectory) and: buildFile << configureTargetMachines("machines.os('some-other-family')") @@ -217,7 +356,7 @@ abstract class AbstractSwiftComponentIntegrationTest extends AbstractNativeLangu def "fails configuration when no target machine is configured"() { given: makeSingleProject() - swift4Component.writeToProject(testDirectory) + componentUnderTest.writeToProject(testDirectory) and: buildFile << configureTargetMachines('') @@ -243,6 +382,8 @@ abstract class AbstractSwiftComponentIntegrationTest extends AbstractNativeLangu abstract SourceElement getSwift4Component() + abstract SourceElement getSwift5Component() + abstract List getTasksToAssembleDevelopmentBinaryOfComponentUnderTest() abstract String getComponentName() diff --git a/subprojects/language-scala/src/integTest/groovy/org/gradle/language/scala/CachedPlatformScalaCompileIntegrationTest.groovy b/subprojects/language-scala/src/integTest/groovy/org/gradle/language/scala/CachedPlatformScalaCompileIntegrationTest.groovy index 3696843e81958..c25ecf5842026 100644 --- a/subprojects/language-scala/src/integTest/groovy/org/gradle/language/scala/CachedPlatformScalaCompileIntegrationTest.groovy +++ b/subprojects/language-scala/src/integTest/groovy/org/gradle/language/scala/CachedPlatformScalaCompileIntegrationTest.groovy @@ -77,7 +77,8 @@ class CachedPlatformScalaCompileIntegrationTest extends AbstractCachedCompileInt then: compiledJavaClass.exists() compiledScalaClass.exists() - output.contains("Caching disabled for task ':compileMainJarMainScala': Gradle does not know how file 'build") + output.contains("Caching disabled for task ':compileMainJarMainScala' because:" + + "\n Gradle does not know how file 'build") } def "incremental compilation works with caching"() { diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/DeploymentContinuousBuildIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/DeploymentContinuousBuildIntegrationTest.groovy new file mode 100644 index 0000000000000..d3c7d23fbdf3c --- /dev/null +++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/continuous/DeploymentContinuousBuildIntegrationTest.groovy @@ -0,0 +1,65 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.launcher.continuous + +import org.gradle.test.fixtures.TestDeploymentFixture + +import static org.gradle.util.CollectionUtils.single + +class DeploymentContinuousBuildIntegrationTest extends Java7RequiringContinuousIntegrationTest { + def fixture = new TestDeploymentFixture() + + def setup() { + fixture.writeToProject(testDirectory) + buildTimeout = 30 + } + + def "deployment promoted to continuous build reports accurate build time" () { + when: + withoutContinuousBuild() + succeeds("runDeployment") + + then: + def key = fixture.keyFile.text + fixture.assertDeploymentIsRunning(key) + buildTimes.size() == 2 + buildTimes[0] >= buildTimes[1] + } + + def "deployment in continuous build reports accurate build time" () { + when: + succeeds("runDeployment") + + then: + def key = fixture.keyFile.text + fixture.assertDeploymentIsRunning(key) + + when: + def lastBuildTime = single(buildTimes) + waitBeforeModification fixture.triggerFile + fixture.triggerFile << "\n#a change" + succeeds() + + then: + fixture.assertDeploymentIsRunning(key) + lastBuildTime >= single(buildTimes) + } + + List getBuildTimes() { + return (output =~ /BUILD SUCCESSFUL in (\d+)s/).collect { it[1].toString().toInteger() } + } +} diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonExpirationIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonExpirationIntegrationTest.groovy new file mode 100644 index 0000000000000..ee549c9f6460c --- /dev/null +++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonExpirationIntegrationTest.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.launcher.daemon + +import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec +import org.gradle.launcher.daemon.server.health.gc.GarbageCollectorMonitoringStrategy + +class DaemonExpirationIntegrationTest extends DaemonIntegrationSpec { + def "daemon understands the GC used"() { + buildFile << """ + import ${GarbageCollectorMonitoringStrategy.canonicalName} + + def strategy = services.get(GarbageCollectorMonitoringStrategy) + assert strategy != GarbageCollectorMonitoringStrategy.UNKNOWN + """ + expect: + run "help" + } +} diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonJvmSettingsIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonJvmSettingsIntegrationTest.groovy index 20fb4e78b764c..ef79793a1dd5b 100644 --- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonJvmSettingsIntegrationTest.groovy +++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonJvmSettingsIntegrationTest.groovy @@ -16,9 +16,11 @@ package org.gradle.launcher.daemon +import org.gradle.api.JavaVersion import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec import org.gradle.util.Requires import org.gradle.util.TestPrecondition +import spock.lang.Unroll @Requires(TestPrecondition.NOT_UNKNOWN_OS) class DaemonJvmSettingsIntegrationTest extends DaemonIntegrationSpec { @@ -34,4 +36,47 @@ assert java.lang.management.ManagementFactory.runtimeMXBean.inputArguments.conta expect: succeeds() } + + @Unroll + def "uses defaults for max/min heap size when JAVA_TOOL_OPTIONS is set (#javaToolOptions)"() { + setup: + executer.requireGradleDistribution() + boolean java9orAbove = JavaVersion.current().java9Compatible + + buildScript """ + import java.lang.management.ManagementFactory + import java.lang.management.MemoryMXBean + + println "GRADLE_VERSION: " + gradle.gradleVersion + + task verify { + doFirst { + MemoryMXBean memBean = ManagementFactory.getMemoryMXBean() + println "Initial Heap: " + memBean.heapMemoryUsage.init + assert memBean.heapMemoryUsage.init == 256 * 1024 * 1024 + println " Max Heap: " + memBean.heapMemoryUsage.max + + // Java 8 does not report max heap size exactly matching the command line setting + if ($java9orAbove) { + assert memBean.heapMemoryUsage.max == 512 * 1024 * 1024 + } else { + assert memBean.heapMemoryUsage.max > 256 * 1024 * 1024 + } + } + } + """ + + when: + // This prevents the executer fixture from adding "default" values to the build jvm options + executer.useOnlyRequestedJvmOpts() + executer.withEnvironmentVars(JAVA_TOOL_OPTIONS: javaToolOptions) + run "verify" + + then: + String gradleVersion = (output =~ /GRADLE_VERSION: (.*)/)[0][1] + daemons(gradleVersion).daemons.size() == 1 + + where: + javaToolOptions << ["-Xms513m", "-Xmx255m", "-Xms128m -Xmx256m"] + } } diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonReportStatusIntegrationSpec.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonReportStatusIntegrationSpec.groovy index 1b0c63c882477..e82c31576fc6f 100644 --- a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonReportStatusIntegrationSpec.groovy +++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/DaemonReportStatusIntegrationSpec.groovy @@ -53,6 +53,7 @@ task block { block.waitForAllPendingCalls() daemons.daemon.assertBusy() + executer.useOnlyRequestedJvmOpts() executer.withBuildJvmOpts('-Xmx128m') executer.run() diff --git a/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitoringIntegrationTest.groovy b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitoringIntegrationTest.groovy new file mode 100644 index 0000000000000..6b9f062725ac1 --- /dev/null +++ b/subprojects/launcher/src/integTest/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitoringIntegrationTest.groovy @@ -0,0 +1,285 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.launcher.daemon.server.health.gc + +import org.gradle.integtests.fixtures.ContextualMultiVersionTest +import org.gradle.integtests.fixtures.MultiVersionSpecRunner +import org.gradle.integtests.fixtures.TargetCoverage +import org.gradle.integtests.fixtures.daemon.DaemonIntegrationSpec +import org.gradle.integtests.fixtures.daemon.JavaGarbageCollector +import org.gradle.launcher.daemon.server.health.DaemonMemoryStatus +import org.gradle.util.TestPrecondition +import org.junit.experimental.categories.Category +import org.junit.runner.RunWith + +import static org.gradle.launcher.daemon.server.DaemonStateCoordinator.DAEMON_STOPPING_IMMEDIATELY_MESSAGE +import static org.gradle.launcher.daemon.server.DaemonStateCoordinator.DAEMON_WILL_STOP_MESSAGE + +@Category(ContextualMultiVersionTest.class) +@RunWith(MultiVersionSpecRunner) +@TargetCoverage({garbageCollectors}) +class GarbageCollectionMonitoringIntegrationTest extends DaemonIntegrationSpec { + static def version + GarbageCollectorUnderTest garbageCollector = version + + def setup() { + executer.withBuildJvmOpts(garbageCollector.configuration.jvmArgs.split(" ")) + executer.withEnvironmentVars(JAVA_TOOL_OPTIONS: "-D${DefaultGarbageCollectionMonitor.DISABLE_POLLING_SYSTEM_PROPERTY}=true -D${DaemonMemoryStatus.ENABLE_PERFORMANCE_MONITORING}=true") + } + + def "expires daemon when heap leaks slowly"() { + given: + configureGarbageCollectionHeapEventsFor(256, 512, 35, garbageCollector.monitoringStrategy.gcRateThreshold + 0.2) + + when: + run "injectEvents" + + then: + daemons.daemon.stops() + + and: + daemons.daemon.log.contains(DAEMON_WILL_STOP_MESSAGE) + } + + def "expires daemon immediately when garbage collector is thrashing"() { + given: + configureGarbageCollectionHeapEventsFor(256, 512, 100, garbageCollector.monitoringStrategy.thrashingThreshold + 0.2) + waitForImmediateDaemonExpiration() + + when: + fails "injectEvents" + + then: + failure.assertHasDescription("Gradle build daemon has been stopped: JVM garbage collector thrashing and after running out of JVM memory") + + and: + daemons.daemon.stops() + + and: + daemons.daemon.log.contains(DAEMON_STOPPING_IMMEDIATELY_MESSAGE) + } + + def "expires daemon when heap leaks while daemon is idle"() { + def initial = 256 + def max = 512 + def events = eventsFor(initial, max, 35, garbageCollector.monitoringStrategy.gcRateThreshold + 0.2) + def initScript = file("init.gradle") + initScript << """ + ${injectionImports} + + gradle.buildFinished { + ${eventInjectionConfiguration("heap", events, initial, max)} + } + + gradle.rootProject { + tasks.create("startLeakAfterBuild") + } + """ + executer.usingInitScript(initScript) + + when: + succeeds "startLeakAfterBuild" + + then: + daemons.daemon.stops() + + and: + daemons.daemon.log.contains(DAEMON_WILL_STOP_MESSAGE) + } + + def "expires daemon when metaspace leaks"() { + given: + configureGarbageCollectionNonHeapEventsFor(256, 512, 35, 0) + + when: + run "injectEvents" + + then: + daemons.daemon.stops() + + and: + daemons.daemon.log.contains(DAEMON_WILL_STOP_MESSAGE) + } + + def "does not expire daemon when leak does not consume heap threshold"() { + given: + configureGarbageCollectionHeapEventsFor(256, 512, 5, garbageCollector.monitoringStrategy.gcRateThreshold + 0.2) + + when: + run "injectEvents" + + then: + daemons.daemon.becomesIdle() + } + + def "does not expire daemon when leak does not cause excessive garbage collection"() { + given: + configureGarbageCollectionHeapEventsFor(256, 512, 35, garbageCollector.monitoringStrategy.gcRateThreshold - 0.2) + + when: + run "injectEvents" + + then: + daemons.daemon.becomesIdle() + } + + def "does not expire daemon when leak does not consume metaspace threshold"() { + given: + configureGarbageCollectionNonHeapEventsFor(256, 512, 5, 0) + + when: + run "injectEvents" + + then: + daemons.daemon.becomesIdle() + } + + void configureGarbageCollectionHeapEventsFor(long initial, long max, long leakRate, double gcRate) { + configureGarbageCollectionEvents("heap", initial, max, leakRate, gcRate) + } + + void configureGarbageCollectionNonHeapEventsFor(long initial, long max, long leakRate, double gcRate) { + configureGarbageCollectionEvents("nonHeap", initial, max, leakRate, gcRate) + } + + void configureGarbageCollectionEvents(String type, long initial, long max, leakRate, gcRate) { + def events = eventsFor(initial, max, leakRate, gcRate) + buildFile << """ + ${injectionImports} + + task injectEvents { + doLast { + ${eventInjectionConfiguration(type, events, initial, max)} + } + } + """ + } + + String getInjectionImports() { + return """ + import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionMonitor + import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionEvent + import org.gradle.launcher.daemon.server.health.DaemonHealthStats + import org.gradle.internal.time.Clock + import org.gradle.internal.time.Time + import java.lang.management.MemoryUsage + """ + } + + String eventInjectionConfiguration(String type, Collection events, long initial, long max) { + return """ + DaemonHealthStats stats = services.get(DaemonHealthStats.class) + GarbageCollectionMonitor monitor = stats.getGcMonitor() + long startTime = Time.clock().getCurrentTime() + ${injectEvents("monitor.get${type.capitalize()}Events()", events, initial, max)} + println "GC rate: " + stats.get${type.capitalize()}Stats().getGcRate() + println " % used: " + stats.get${type.capitalize()}Stats().getUsedPercent() + "%" + """ + } + + void waitForImmediateDaemonExpiration() { + buildFile << """ + import org.gradle.internal.event.ListenerManager + import org.gradle.launcher.daemon.server.expiry.DaemonExpirationListener + import org.gradle.launcher.daemon.server.expiry.DaemonExpirationResult + import java.util.concurrent.CountDownLatch + import java.util.concurrent.TimeUnit + + def latch = new CountDownLatch(1) + services.get(ListenerManager.class).addListener(new DaemonExpirationListener() { + void onExpirationEvent(DaemonExpirationResult result) { + latch.countDown() + } + }) + + injectEvents.doLast { + // Wait for a daemon expiration event to occur + latch.await(6, TimeUnit.SECONDS) + // Give the monitor a chance to stop the daemon abruptly + sleep 6000 + } + """ + } + + String injectEvents(String getter, Collection events, long initial, long max) { + StringBuilder builder = new StringBuilder() + events.each { Map event -> + builder.append("${getter}.slideAndInsert(new GarbageCollectionEvent(startTime + ${fromSeconds(event.timeOffset)}, ${usageFrom(initial, max, event.poolUsage)}, ${event.gcCount}))\n") + } + return builder.toString() + } + + /** + * Generates garbage collection events starting at initial heap size (MB) and increasing by + * leakRate (MB) for every event, registering a variable number of garbage collections + * according to gcRate. Heap usage will cap at max. + */ + Collection eventsFor(long initial, long max, long leakRate, double gcRate) { + def events = [] + long usage = initial + long gcCount = 0 + 20.times { count -> + usage += leakRate + if (usage > max) { + usage = max + } + gcCount = (gcRate * count) as long + events << ["timeOffset": count, "poolUsage": usage, "gcCount": gcCount] + } + return events + } + + long fromMB(long sizeInMB) { + return sizeInMB * 1024 * 1024 + } + + long fromSeconds(long seconds) { + return seconds * 1000 + } + + String usageFrom(long initial, long max, long used) { + return "new MemoryUsage(${fromMB(initial)}, ${fromMB(used)}, ${fromMB(used)}, ${fromMB(max)})" + } + + static List getGarbageCollectors() { + if (TestPrecondition.JDK_IBM.fulfilled) { + return [ new GarbageCollectorUnderTest(JavaGarbageCollector.IBM_ALL, GarbageCollectorMonitoringStrategy.IBM_ALL) ] + } else { + return [ + new GarbageCollectorUnderTest(JavaGarbageCollector.ORACLE_PARALLEL_CMS, GarbageCollectorMonitoringStrategy.ORACLE_PARALLEL_CMS), + new GarbageCollectorUnderTest(JavaGarbageCollector.ORACLE_SERIAL9, GarbageCollectorMonitoringStrategy.ORACLE_SERIAL), + new GarbageCollectorUnderTest(JavaGarbageCollector.ORACLE_G1, GarbageCollectorMonitoringStrategy.ORACLE_G1) + ] + } + } + + static class GarbageCollectorUnderTest { + final JavaGarbageCollector configuration + final GarbageCollectorMonitoringStrategy monitoringStrategy + + GarbageCollectorUnderTest(JavaGarbageCollector configuration, GarbageCollectorMonitoringStrategy monitoringStrategy) { + this.configuration = configuration + this.monitoringStrategy = monitoringStrategy + } + + + @Override + public String toString() { + return configuration.name() + } + } +} diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java index dee2aa6c0fec1..e644fce4ecda1 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/configuration/DaemonParameters.java @@ -35,8 +35,8 @@ public class DaemonParameters { static final int DEFAULT_IDLE_TIMEOUT = 3 * 60 * 60 * 1000; public static final int DEFAULT_PERIODIC_CHECK_INTERVAL_MILLIS = 10 * 1000; - public static final List DEFAULT_JVM_ARGS = ImmutableList.of("-Xmx512m", "-XX:MaxPermSize=256m", "-XX:+HeapDumpOnOutOfMemoryError"); - public static final List DEFAULT_JVM_8_ARGS = ImmutableList.of("-Xmx512m", "-XX:MaxMetaspaceSize=256m", "-XX:+HeapDumpOnOutOfMemoryError"); + public static final List DEFAULT_JVM_ARGS = ImmutableList.of("-Xmx512m", "-Xms256m", "-XX:MaxPermSize=256m", "-XX:+HeapDumpOnOutOfMemoryError"); + public static final List DEFAULT_JVM_8_ARGS = ImmutableList.of("-Xmx512m", "-Xms256m", "-XX:MaxMetaspaceSize=256m", "-XX:+HeapDumpOnOutOfMemoryError"); public static final List ALLOW_ENVIRONMENT_VARIABLE_OVERWRITE = ImmutableList.of("--add-opens", "java.base/java.util=ALL-UNNAMED"); private final File gradleUserHomeDir; diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistry.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistry.java index 54b74f8c7d38d..a284c4d6d7354 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistry.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/registry/DaemonRegistry.java @@ -16,7 +16,7 @@ package org.gradle.launcher.daemon.registry; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.remote.Address; import java.util.Collection; diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java index 333989ec857ed..1829ed70e7243 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/DaemonServices.java @@ -58,6 +58,7 @@ import org.gradle.launcher.daemon.server.health.DaemonHealthStats; import org.gradle.launcher.daemon.server.health.DaemonMemoryStatus; import org.gradle.launcher.daemon.server.health.HealthExpirationStrategy; +import org.gradle.launcher.daemon.server.health.gc.GarbageCollectorMonitoringStrategy; import org.gradle.launcher.daemon.server.scaninfo.DaemonScanInfo; import org.gradle.launcher.daemon.server.scaninfo.DefaultDaemonScanInfo; import org.gradle.launcher.daemon.server.stats.DaemonRunningStats; @@ -104,8 +105,8 @@ public File getDaemonLogFile() { return new File(get(DaemonDir.class).getVersionedDir(), fileName); } - protected DaemonMemoryStatus createDaemonMemoryStatus(DaemonHealthStats healthStats) { - return new DaemonMemoryStatus(healthStats); + protected DaemonMemoryStatus createDaemonMemoryStatus(DaemonHealthStats healthStats, GarbageCollectorMonitoringStrategy strategy) { + return new DaemonMemoryStatus(healthStats, strategy.getHeapUsageThreshold(), strategy.getGcRateThreshold(), strategy.getNonHeapUsageThreshold(), strategy.getThrashingThreshold()); } protected DaemonHealthCheck createDaemonHealthCheck(ListenerManager listenerManager, HealthExpirationStrategy healthExpirationStrategy) { @@ -128,8 +129,12 @@ protected HealthExpirationStrategy createHealthExpirationStrategy(DaemonMemorySt return new HealthExpirationStrategy(memoryStatus); } - protected DaemonHealthStats createDaemonHealthStats(DaemonRunningStats runningStats, ExecutorFactory executorFactory) { - return new DaemonHealthStats(runningStats, executorFactory); + protected DaemonHealthStats createDaemonHealthStats(DaemonRunningStats runningStats, GarbageCollectorMonitoringStrategy strategy, ExecutorFactory executorFactory) { + return new DaemonHealthStats(runningStats, strategy, executorFactory); + } + + protected GarbageCollectorMonitoringStrategy createGarbageCollectorMonitoringStrategy() { + return GarbageCollectorMonitoringStrategy.determineGcStrategy(); } protected ImmutableList createDaemonCommandActions(DaemonContext daemonContext, ProcessEnvironment processEnvironment, DaemonHealthStats healthStats, DaemonHealthCheck healthCheck, BuildExecuter buildActionExecuter, DaemonRunningStats runningStats) { diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/DaemonHealthStats.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/DaemonHealthStats.java index 2b22c88b4552e..1585b031a380c 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/DaemonHealthStats.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/DaemonHealthStats.java @@ -18,9 +18,10 @@ import com.google.common.annotations.VisibleForTesting; import org.gradle.internal.concurrent.ExecutorFactory; -import org.gradle.internal.concurrent.Stoppable; import org.gradle.internal.concurrent.ManagedScheduledExecutor; +import org.gradle.internal.concurrent.Stoppable; import org.gradle.internal.util.NumberUtil; +import org.gradle.launcher.daemon.server.health.gc.DefaultGarbageCollectionMonitor; import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionInfo; import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionMonitor; import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionStats; @@ -34,13 +35,13 @@ public class DaemonHealthStats implements Stoppable { private final DaemonRunningStats runningStats; private final ManagedScheduledExecutor scheduler; private final GarbageCollectionInfo gcInfo; - private final GarbageCollectionMonitor gcMonitor; + private GarbageCollectionMonitor gcMonitor; - public DaemonHealthStats(DaemonRunningStats runningStats, ExecutorFactory executorFactory) { + public DaemonHealthStats(DaemonRunningStats runningStats, GarbageCollectorMonitoringStrategy strategy, ExecutorFactory executorFactory) { this.runningStats = runningStats; this.scheduler = executorFactory.createScheduled("Daemon health stats", 1); this.gcInfo = new GarbageCollectionInfo(); - this.gcMonitor = new GarbageCollectionMonitor(scheduler); + this.gcMonitor = new DefaultGarbageCollectionMonitor(strategy, scheduler); } @VisibleForTesting @@ -51,6 +52,11 @@ public DaemonHealthStats(DaemonRunningStats runningStats, ExecutorFactory execut this.gcMonitor = gcMonitor; } + @VisibleForTesting + public GarbageCollectionMonitor getGcMonitor() { + return gcMonitor; + } + @Override public void stop() { if (scheduler != null) { @@ -58,8 +64,12 @@ public void stop() { } } - GarbageCollectionMonitor getGcMonitor() { - return gcMonitor; + public GarbageCollectionStats getHeapStats() { + return gcMonitor.getHeapStats(); + } + + public GarbageCollectionStats getNonHeapStats() { + return gcMonitor.getNonHeapStats(); } /** @@ -79,26 +89,23 @@ private String getFirstBuildHealthInfo() { } private String getBuildHealthInfo(int nextBuildNum) { - if (gcMonitor.getGcStrategy() != GarbageCollectorMonitoringStrategy.UNKNOWN) { - GarbageCollectionStats tenuredStats = gcMonitor.getTenuredStats(); - GarbageCollectionStats permgenStats = gcMonitor.getPermGenStats(); - String message = format("Starting %s build in daemon [uptime: %s, performance: %s%%", - NumberUtil.ordinal(nextBuildNum), runningStats.getPrettyUpTime(), getCurrentPerformance()); - if (tenuredStats.getUsage() > 0) { - message += format(", GC rate: %.2f/s, tenured heap usage: %s%% of %s", tenuredStats.getRate(), tenuredStats.getUsage(), NumberUtil.formatBytes(tenuredStats.getMax())); - if (permgenStats.getUsage() > 0) { - message += format(", perm gen usage: %s%% of %s", - permgenStats.getUsage(), NumberUtil.formatBytes(permgenStats.getMax())); - } - } else { - message += ", no major garbage collections"; - } - message += "]"; - return message; - } else { - return format("Starting %s build in daemon [uptime: %s, performance: %s%%]", - NumberUtil.ordinal(nextBuildNum), runningStats.getPrettyUpTime(), getCurrentPerformance()); + + StringBuilder message = new StringBuilder(format("Starting %s build in daemon ", NumberUtil.ordinal(nextBuildNum))); + message.append(format("[uptime: %s, performance: %s%%", runningStats.getPrettyUpTime(), getCurrentPerformance())); + + GarbageCollectionStats heapStats = gcMonitor.getHeapStats(); + if (heapStats.isValid()) { + message.append(format(", GC rate: %.2f/s", heapStats.getGcRate())); + message.append(format(", heap usage: %s%% of %s", heapStats.getUsedPercent(), NumberUtil.formatBytes(heapStats.getMaxSizeInBytes()))); } + + GarbageCollectionStats nonHeapStats = gcMonitor.getNonHeapStats(); + if (nonHeapStats.isValid()) { + message.append(format(", non-heap usage: %s%% of %s", nonHeapStats.getUsedPercent(), NumberUtil.formatBytes(nonHeapStats.getMaxSizeInBytes()))); + } + message.append("]"); + + return message.toString(); } /** diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/DaemonMemoryStatus.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/DaemonMemoryStatus.java index 6b75cc7fa1e63..38df8f6d6d70f 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/DaemonMemoryStatus.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/DaemonMemoryStatus.java @@ -16,96 +16,83 @@ package org.gradle.launcher.daemon.server.health; -import org.gradle.api.GradleException; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.api.specs.Spec; import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionStats; -import org.gradle.launcher.daemon.server.health.gc.GarbageCollectorMonitoringStrategy; - -import static java.lang.String.format; public class DaemonMemoryStatus { private static final Logger LOGGER = Logging.getLogger(DaemonMemoryStatus.class); public static final String ENABLE_PERFORMANCE_MONITORING = "org.gradle.daemon.performance.enable-monitoring"; - public static final String TENURED_USAGE_EXPIRE_AT = "org.gradle.daemon.performance.tenured-usage-expire-at"; - public static final String TENURED_RATE_EXPIRE_AT = "org.gradle.daemon.performance.tenured-rate-expire-at"; - public static final String PERMGEN_USAGE_EXPIRE_AT = "org.gradle.daemon.performance.permgen-usage-expire-at"; - public static final String THRASHING_EXPIRE_AT = "org.gradle.daemon.performance.thrashing-expire-at"; - private static final String TENURED = "tenured"; - private static final String PERMGEN = "perm gen"; + private static final String HEAP = "heap"; + private static final String NON_HEAP = "non-heap"; private final DaemonHealthStats stats; - private final GarbageCollectorMonitoringStrategy strategy; - private final int tenuredUsageThreshold; - private final double tenuredRateThreshold; - private final int permgenUsageThreshold; + private final int heapUsageThreshold; + private final double heapRateThreshold; + private final int nonHeapUsageThreshold; private final double thrashingThreshold; - public DaemonMemoryStatus(DaemonHealthStats stats) { + public DaemonMemoryStatus(DaemonHealthStats stats, int heapUsageThreshold, double heapRateThreshold, int nonHeapUsageThreshold, double thrashingThreshold) { this.stats = stats; - this.strategy = stats.getGcMonitor().getGcStrategy(); - this.tenuredUsageThreshold = parseValue(TENURED_USAGE_EXPIRE_AT, strategy.getTenuredUsageThreshold()); - this.tenuredRateThreshold = parseValue(TENURED_RATE_EXPIRE_AT, strategy.getGcRateThreshold()); - this.permgenUsageThreshold = parseValue(PERMGEN_USAGE_EXPIRE_AT, strategy.getPermGenUsageThreshold()); - this.thrashingThreshold = parseValue(THRASHING_EXPIRE_AT, strategy.getThrashingThreshold()); + this.heapUsageThreshold = heapUsageThreshold; + this.heapRateThreshold = heapRateThreshold; + this.nonHeapUsageThreshold = nonHeapUsageThreshold; + this.thrashingThreshold = thrashingThreshold; } - public boolean isTenuredSpaceExhausted() { - GarbageCollectionStats gcStats = stats.getGcMonitor().getTenuredStats(); + public boolean isHeapSpaceExhausted() { + GarbageCollectionStats gcStats = stats.getHeapStats(); - return exceedsThreshold(TENURED, gcStats, new Spec() { + return exceedsThreshold(HEAP, gcStats, new Spec() { @Override public boolean isSatisfiedBy(GarbageCollectionStats gcStats) { - return tenuredUsageThreshold != 0 - && tenuredRateThreshold != 0 - && gcStats.getEventCount() >= 5 - && gcStats.getUsage() >= tenuredUsageThreshold - && gcStats.getRate() >= tenuredRateThreshold; + return heapUsageThreshold != 0 + && heapRateThreshold != 0 + && gcStats.isValid() + && gcStats.getUsedPercent() >= heapUsageThreshold + && gcStats.getGcRate() >= heapRateThreshold; } }); } - public boolean isPermGenSpaceExhausted() { - GarbageCollectionStats gcStats = stats.getGcMonitor().getPermGenStats(); + public boolean isNonHeapSpaceExhausted() { + GarbageCollectionStats gcStats = stats.getNonHeapStats(); - return exceedsThreshold(PERMGEN, gcStats, new Spec() { + return exceedsThreshold(NON_HEAP, gcStats, new Spec() { @Override public boolean isSatisfiedBy(GarbageCollectionStats gcStats) { - return permgenUsageThreshold != 0 - && gcStats.getEventCount() >= 5 - && gcStats.getUsage() >= permgenUsageThreshold; + return nonHeapUsageThreshold != 0 + && gcStats.isValid() + && gcStats.getUsedPercent() >= nonHeapUsageThreshold; } }); } public boolean isThrashing() { - GarbageCollectionStats gcStats = stats.getGcMonitor().getTenuredStats(); + GarbageCollectionStats gcStats = stats.getHeapStats(); - return exceedsThreshold(TENURED, gcStats, new Spec() { + return exceedsThreshold(HEAP, gcStats, new Spec() { @Override public boolean isSatisfiedBy(GarbageCollectionStats gcStats) { - return tenuredUsageThreshold != 0 + return heapUsageThreshold != 0 && thrashingThreshold != 0 - && gcStats.getEventCount() >= 5 - && gcStats.getUsage() >= tenuredUsageThreshold - && gcStats.getRate() >= thrashingThreshold; + && gcStats.isValid() + && gcStats.getUsedPercent() >= heapUsageThreshold + && gcStats.getGcRate() >= thrashingThreshold; } }); } private boolean exceedsThreshold(String pool, GarbageCollectionStats gcStats, Spec spec) { - if (isEnabled() - && strategy != GarbageCollectorMonitoringStrategy.UNKNOWN - && spec.isSatisfiedBy(gcStats)) { - - if (gcStats.getUsage() > 0) { - LOGGER.debug(String.format("GC rate: %.2f/s %s usage: %s%%", gcStats.getRate(), pool, gcStats.getUsage())); + if (isEnabled() && spec.isSatisfiedBy(gcStats)) { + if (gcStats.isValid() && gcStats.getUsedPercent() > 0) { + LOGGER.debug(String.format("%s: GC rate: %.2f/s %s usage: %s%%", pool, gcStats.getGcRate(), pool, gcStats.getUsedPercent())); } else { - LOGGER.debug("GC rate: 0.0/s"); + LOGGER.debug("{}: GC rate: 0.0/s", pool); } return true; @@ -118,34 +105,4 @@ private boolean isEnabled() { String enabledValue = System.getProperty(ENABLE_PERFORMANCE_MONITORING, "true"); return Boolean.parseBoolean(enabledValue); } - - private static int parseValue(String property, int defaultValue) { - String expireAt = System.getProperty(property); - - if (expireAt == null) { - return defaultValue; - } - try { - return Integer.parseInt(expireAt); - } catch (Exception e) { - throw new GradleException(format( - "System property '%s' has incorrect value: '%s'. The value needs to be an integer.", - property, expireAt)); - } - } - - private static double parseValue(String property, double defaultValue) { - String expireAt = System.getProperty(property); - - if (expireAt == null) { - return defaultValue; - } - try { - return Double.parseDouble(expireAt); - } catch (Exception e) { - throw new GradleException(format( - "System property '%s' has incorrect value: '%s'. The value needs to be a double.", - property, expireAt)); - } - } } diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/HealthExpirationStrategy.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/HealthExpirationStrategy.java index d8480a40f6dfd..c4f258eb00da4 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/HealthExpirationStrategy.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/HealthExpirationStrategy.java @@ -28,8 +28,8 @@ public class HealthExpirationStrategy implements DaemonExpirationStrategy { public HealthExpirationStrategy(DaemonMemoryStatus memoryStatus) { this.strategy = new AnyDaemonExpirationStrategy(ImmutableList.of( new GcThrashingDaemonExpirationStrategy(memoryStatus), - new LowTenuredSpaceDaemonExpirationStrategy(memoryStatus), - new LowPermGenDaemonExpirationStrategy(memoryStatus) + new LowHeapSpaceDaemonExpirationStrategy(memoryStatus), + new LowNonHeapDaemonExpirationStrategy(memoryStatus) )); } diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowTenuredSpaceDaemonExpirationStrategy.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowHeapSpaceDaemonExpirationStrategy.java similarity index 80% rename from subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowTenuredSpaceDaemonExpirationStrategy.java rename to subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowHeapSpaceDaemonExpirationStrategy.java index 2e44a16ea8f00..0337c53da3beb 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowTenuredSpaceDaemonExpirationStrategy.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowHeapSpaceDaemonExpirationStrategy.java @@ -23,20 +23,20 @@ import static org.gradle.launcher.daemon.server.expiry.DaemonExpirationStatus.GRACEFUL_EXPIRE; -public class LowTenuredSpaceDaemonExpirationStrategy implements DaemonExpirationStrategy { +public class LowHeapSpaceDaemonExpirationStrategy implements DaemonExpirationStrategy { private final DaemonMemoryStatus status; - private static final Logger LOG = Logging.getLogger(LowTenuredSpaceDaemonExpirationStrategy.class); + private static final Logger LOG = Logging.getLogger(LowHeapSpaceDaemonExpirationStrategy.class); public static final String EXPIRATION_REASON = "after running out of JVM memory"; - public static final String EXPIRE_DAEMON_MESSAGE = "Expiring Daemon because JVM Tenured space is exhausted"; + public static final String EXPIRE_DAEMON_MESSAGE = "Expiring Daemon because JVM heap space is exhausted"; - public LowTenuredSpaceDaemonExpirationStrategy(DaemonMemoryStatus status) { + public LowHeapSpaceDaemonExpirationStrategy(DaemonMemoryStatus status) { this.status = status; } @Override public DaemonExpirationResult checkExpiration() { - if (status.isTenuredSpaceExhausted()) { + if (status.isHeapSpaceExhausted()) { LOG.warn(EXPIRE_DAEMON_MESSAGE); return new DaemonExpirationResult(GRACEFUL_EXPIRE, EXPIRATION_REASON); } else { diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowPermGenDaemonExpirationStrategy.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowNonHeapDaemonExpirationStrategy.java similarity index 79% rename from subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowPermGenDaemonExpirationStrategy.java rename to subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowNonHeapDaemonExpirationStrategy.java index c786a80831676..19e0f293d6a1b 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowPermGenDaemonExpirationStrategy.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/LowNonHeapDaemonExpirationStrategy.java @@ -23,20 +23,20 @@ import static org.gradle.launcher.daemon.server.expiry.DaemonExpirationStatus.GRACEFUL_EXPIRE; -public class LowPermGenDaemonExpirationStrategy implements DaemonExpirationStrategy { +public class LowNonHeapDaemonExpirationStrategy implements DaemonExpirationStrategy { private final DaemonMemoryStatus status; - private static final Logger LOG = Logging.getLogger(LowPermGenDaemonExpirationStrategy.class); + private static final Logger LOG = Logging.getLogger(LowNonHeapDaemonExpirationStrategy.class); public static final String EXPIRATION_REASON = "after running out of JVM memory"; - public LowPermGenDaemonExpirationStrategy(DaemonMemoryStatus status) { + public LowNonHeapDaemonExpirationStrategy(DaemonMemoryStatus status) { this.status = status; } @Override public DaemonExpirationResult checkExpiration() { - if (status.isPermGenSpaceExhausted()) { - LOG.info("Expiring Daemon due to JVM PermGen space being exhausted"); + if (status.isNonHeapSpaceExhausted()) { + LOG.info("Expiring Daemon due to JVM Metaspace space being exhausted"); return new DaemonExpirationResult(GRACEFUL_EXPIRE, EXPIRATION_REASON); } else { return DaemonExpirationResult.NOT_TRIGGERED; diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/DefaultGarbageCollectionMonitor.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/DefaultGarbageCollectionMonitor.java new file mode 100644 index 0000000000000..6ab8d4bff66c9 --- /dev/null +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/DefaultGarbageCollectionMonitor.java @@ -0,0 +1,80 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.launcher.daemon.server.health.gc; + +import com.google.common.annotations.VisibleForTesting; +import org.gradle.api.specs.Spec; +import org.gradle.internal.time.Time; +import org.gradle.util.CollectionUtils; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class DefaultGarbageCollectionMonitor implements GarbageCollectionMonitor { + public static final String DISABLE_POLLING_SYSTEM_PROPERTY = "org.gradle.daemon.gc.polling.disabled"; + + private static final int POLL_INTERVAL_SECONDS = 1; + private static final int POLL_DELAY_SECONDS = 1; + private static final int EVENT_WINDOW = 20; + + private final SlidingWindow heapEvents; + private final SlidingWindow nonHeapEvents; + private final GarbageCollectorMonitoringStrategy gcStrategy; + private final ScheduledExecutorService pollingExecutor; + + public DefaultGarbageCollectionMonitor(GarbageCollectorMonitoringStrategy gcStrategy, ScheduledExecutorService pollingExecutor) { + this.pollingExecutor = pollingExecutor; + this.gcStrategy = gcStrategy; + this.heapEvents = new DefaultSlidingWindow(EVENT_WINDOW); + this.nonHeapEvents = new DefaultSlidingWindow(EVENT_WINDOW); + if (gcStrategy != GarbageCollectorMonitoringStrategy.UNKNOWN && !Boolean.getBoolean(DISABLE_POLLING_SYSTEM_PROPERTY)) { + pollForValues(); + } + } + + private void pollForValues() { + GarbageCollectorMXBean garbageCollectorMXBean = CollectionUtils.findFirst(ManagementFactory.getGarbageCollectorMXBeans(), new Spec() { + @Override + public boolean isSatisfiedBy(GarbageCollectorMXBean element) { + return element.getName().equals(gcStrategy.getGarbageCollectorName()); + } + }); + pollingExecutor.scheduleAtFixedRate(new GarbageCollectionCheck(Time.clock(), garbageCollectorMXBean, gcStrategy.getHeapPoolName(), heapEvents, gcStrategy.getNonHeapPoolName(), nonHeapEvents), POLL_DELAY_SECONDS, POLL_INTERVAL_SECONDS, TimeUnit.SECONDS); + } + + @Override + public GarbageCollectionStats getHeapStats() { + return GarbageCollectionStats.forHeap(heapEvents.snapshot()); + } + + @Override + public GarbageCollectionStats getNonHeapStats() { + return GarbageCollectionStats.forNonHeap(nonHeapEvents.snapshot()); + } + + @VisibleForTesting + public SlidingWindow getHeapEvents() { + return heapEvents; + } + + @VisibleForTesting + public SlidingWindow getNonHeapEvents() { + return nonHeapEvents; + } +} diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/DefaultSlidingWindow.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/DefaultSlidingWindow.java index 68cc5a8893514..1138a88a96a55 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/DefaultSlidingWindow.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/DefaultSlidingWindow.java @@ -18,7 +18,7 @@ import com.google.common.collect.Sets; -import java.util.Set; +import java.util.Collection; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.locks.ReentrantLock; @@ -43,7 +43,7 @@ public void slideAndInsert(T element) { } @Override - public Set snapshot() { + public Collection snapshot() { lock.lock(); try { return Sets.newLinkedHashSet(deque); @@ -51,4 +51,14 @@ public Set snapshot() { lock.unlock(); } } + + @Override + public T latest() { + lock.lock(); + try { + return deque.peekLast(); + } finally { + lock.unlock(); + } + } } diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionCheck.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionCheck.java index 9bee6e54d9eaf..c2c91a103fbc6 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionCheck.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionCheck.java @@ -16,42 +16,49 @@ package org.gradle.launcher.daemon.server.health.gc; -import org.gradle.api.specs.Spec; -import org.gradle.util.CollectionUtils; +import org.gradle.internal.time.Clock; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryType; import java.util.List; -import java.util.Map; public class GarbageCollectionCheck implements Runnable { - final Map> events; - final List memoryPools; - final String garbageCollector; - - public GarbageCollectionCheck(Map> events, List memoryPools, String garbageCollector) { - this.events = events; - this.memoryPools = memoryPools; - this.garbageCollector = garbageCollector; + + private final Clock clock; + private final GarbageCollectorMXBean garbageCollectorMXBean; + + private final String heapMemoryPool; + private final SlidingWindow heapEvents; + + private final String nonHeapMemoryPool; + private final SlidingWindow nonHeapEvents; + + public GarbageCollectionCheck(Clock clock, GarbageCollectorMXBean garbageCollectorMXBean, String heapMemoryPool, SlidingWindow heapEvents, String nonHeapMemoryPool, SlidingWindow nonHeapEvents) { + this.clock = clock; + this.garbageCollectorMXBean = garbageCollectorMXBean; + this.heapMemoryPool = heapMemoryPool; + this.heapEvents = heapEvents; + this.nonHeapMemoryPool = nonHeapMemoryPool; + this.nonHeapEvents = nonHeapEvents; } @Override public void run() { - List garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); - GarbageCollectorMXBean garbageCollectorMXBean = CollectionUtils.findFirst(garbageCollectorMXBeans, new Spec() { - @Override - public boolean isSatisfiedBy(GarbageCollectorMXBean mbean) { - return mbean.getName().equals(garbageCollector); - } - }); - List memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans(); for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans) { - String pool = memoryPoolMXBean.getName(); - if (memoryPools.contains(pool)) { - GarbageCollectionEvent event = new GarbageCollectionEvent(System.currentTimeMillis(), memoryPoolMXBean.getCollectionUsage(), garbageCollectorMXBean.getCollectionCount()); - events.get(pool).slideAndInsert(event); + String poolName = memoryPoolMXBean.getName(); + if (memoryPoolMXBean.getType() == MemoryType.HEAP && poolName.equals(heapMemoryPool)) { + GarbageCollectionEvent latest = heapEvents.latest(); + long currentCount = garbageCollectorMXBean.getCollectionCount(); + // There has been a GC event + if (latest == null || latest.getCount() != currentCount) { + heapEvents.slideAndInsert(new GarbageCollectionEvent(clock.getCurrentTime(), memoryPoolMXBean.getCollectionUsage(), currentCount)); + } + } + if (memoryPoolMXBean.getType() == MemoryType.NON_HEAP && poolName.equals(nonHeapMemoryPool)) { + nonHeapEvents.slideAndInsert(new GarbageCollectionEvent(clock.getCurrentTime(), memoryPoolMXBean.getUsage(), -1)); } } } diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionEvent.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionEvent.java index f1887cc6a87fe..9c2be7a5387f6 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionEvent.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionEvent.java @@ -19,9 +19,9 @@ import java.lang.management.MemoryUsage; public class GarbageCollectionEvent { - final long timestamp; - final MemoryUsage usage; - final long count; + private final long timestamp; + private final MemoryUsage usage; + private final long count; public GarbageCollectionEvent(long timestamp, MemoryUsage usage, long count) { this.timestamp = timestamp; diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitor.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitor.java index 1a67343e5dcc2..f7339b9da07be 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitor.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitor.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,134 +16,14 @@ package org.gradle.launcher.daemon.server.health.gc; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import org.gradle.api.Transformer; -import org.gradle.api.logging.Logger; -import org.gradle.api.logging.Logging; -import org.gradle.api.specs.Spec; -import org.gradle.util.CollectionUtils; +public interface GarbageCollectionMonitor { + /** + * Returns a {@link GarbageCollectionStats} object for the tenured heap of the daemon process. + */ + GarbageCollectionStats getHeapStats(); -import java.lang.management.GarbageCollectorMXBean; -import java.lang.management.ManagementFactory; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -public class GarbageCollectionMonitor { - public static final int POLL_INTERVAL_SECONDS = 1; - private static final int POLL_DELAY_SECONDS = 1; - private static final int EVENT_WINDOW = 20; - private static final Logger LOGGER = Logging.getLogger(GarbageCollectionMonitor.class); - private final Map> events; - private final GarbageCollectorMonitoringStrategy gcStrategy; - private final ScheduledExecutorService pollingExecutor; - - public GarbageCollectionMonitor(ScheduledExecutorService pollingExecutor) { - this(determineGcStrategy(), pollingExecutor); - } - - public GarbageCollectionMonitor(GarbageCollectorMonitoringStrategy gcStrategy, ScheduledExecutorService pollingExecutor) { - this.pollingExecutor = pollingExecutor; - this.gcStrategy = gcStrategy; - - if (gcStrategy != GarbageCollectorMonitoringStrategy.UNKNOWN) { - events = ImmutableMap.>of( - gcStrategy.getTenuredPoolName(), new DefaultSlidingWindow(EVENT_WINDOW), - gcStrategy.getPermGenPoolName(), new DefaultSlidingWindow(EVENT_WINDOW) - ); - pollForValues(gcStrategy.getGarbageCollectorName(), ImmutableList.copyOf(events.keySet())); - } else { - events = ImmutableMap.>builder().build(); - } - } - - private static GarbageCollectorMonitoringStrategy determineGcStrategy() { - JVMStrategy jvmStrategy = JVMStrategy.current(); - - final List garbageCollectors = CollectionUtils.collect(ManagementFactory.getGarbageCollectorMXBeans(), new Transformer() { - @Override - public String transform(GarbageCollectorMXBean garbageCollectorMXBean) { - return garbageCollectorMXBean.getName(); - } - }); - GarbageCollectorMonitoringStrategy gcStrategy = CollectionUtils.findFirst(jvmStrategy.getGcStrategies(), new Spec() { - @Override - public boolean isSatisfiedBy(GarbageCollectorMonitoringStrategy strategy) { - return garbageCollectors.contains(strategy.getGarbageCollectorName()); - } - }); - - if (gcStrategy == null) { - LOGGER.info("Unable to determine a garbage collection monitoring strategy for " + jvmStrategy.toString()); - return GarbageCollectorMonitoringStrategy.UNKNOWN; - } else { - return gcStrategy; - } - } - - private void pollForValues(String garbageCollectorName, List memoryPoolNames) { - pollingExecutor.scheduleAtFixedRate(new GarbageCollectionCheck(events, memoryPoolNames, garbageCollectorName), POLL_DELAY_SECONDS, POLL_INTERVAL_SECONDS, TimeUnit.SECONDS); - } - - public GarbageCollectionStats getTenuredStats() { - return getGarbageCollectionStatsWithEmptyDefault(gcStrategy.getTenuredPoolName()); - } - - public GarbageCollectionStats getPermGenStats() { - return getGarbageCollectionStatsWithEmptyDefault(gcStrategy.getPermGenPoolName()); - } - - private GarbageCollectionStats getGarbageCollectionStatsWithEmptyDefault(final String memoryPoolName) { - SlidingWindow slidingWindow; - if ((memoryPoolName == null) || events.get(memoryPoolName) == null) { // events has no entries on UNKNOWN - slidingWindow = new DefaultSlidingWindow(EVENT_WINDOW); - } else { - slidingWindow = events.get(memoryPoolName); - } - return new GarbageCollectionStats(slidingWindow.snapshot()); - } - - public GarbageCollectorMonitoringStrategy getGcStrategy() { - return gcStrategy; - } - - public enum JVMStrategy { - IBM(GarbageCollectorMonitoringStrategy.IBM_ALL), - ORACLE_HOTSPOT(GarbageCollectorMonitoringStrategy.ORACLE_PARALLEL_CMS, - GarbageCollectorMonitoringStrategy.ORACLE_SERIAL, - GarbageCollectorMonitoringStrategy.ORACLE_6_CMS, - GarbageCollectorMonitoringStrategy.ORACLE_G1), - UNSUPPORTED(); - - final GarbageCollectorMonitoringStrategy[] gcStrategies; - - JVMStrategy(GarbageCollectorMonitoringStrategy... gcStrategies) { - this.gcStrategies = gcStrategies; - } - - static JVMStrategy current() { - String vmname = System.getProperty("java.vm.name"); - - if (vmname.equals("IBM J9 VM")) { - return IBM; - } - - if (vmname.startsWith("Java HotSpot(TM)")) { - return ORACLE_HOTSPOT; - } - - return UNSUPPORTED; - } - - public GarbageCollectorMonitoringStrategy[] getGcStrategies() { - return gcStrategies; - } - - @Override - public String toString() { - return "JVMStrategy{" + System.getProperty("java.vendor") + " version " + System.getProperty("java.version") + "}"; - } - } + /** + * Returns a {@link GarbageCollectionStats} object for the metaspace region of the daemon process. + */ + GarbageCollectionStats getNonHeapStats(); } diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionStats.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionStats.java index 7c0d318987501..04362d7bab879 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionStats.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionStats.java @@ -16,118 +16,100 @@ package org.gradle.launcher.daemon.server.health.gc; +import com.google.common.collect.Iterables; import org.gradle.internal.util.NumberUtil; -import java.lang.management.MemoryUsage; -import java.util.Set; +import java.util.Collection; +import java.util.concurrent.TimeUnit; public class GarbageCollectionStats { - final private double rate; - final private long used; - final private long max; - final private long eventCount; - - public GarbageCollectionStats(Set events) { - this.rate = calculateRate(events); - this.used = calculateAverageUsage(events); - this.max = calculateMaxSize(events); - this.eventCount = events.size(); - } - - static double calculateRate(Set events) { - long firstGC = 0; - long lastGC = 0; - long firstCount = 0; - long lastCount = 0; - for (GarbageCollectionEvent event : events) { - // Skip if this was a polling event and the garbage collector did not fire in between events - if (event.getCount() == lastCount || event.getCount() == 0) { - continue; - } - - lastCount = event.getCount(); - - if (firstGC == 0) { - firstGC = event.getTimestamp(); - firstCount = event.getCount(); - } else { - lastGC = event.getTimestamp(); - } + private final double gcRate; + private final int usedPercent; + private final long maxSizeInBytes; + private final long eventCount; + + private GarbageCollectionStats(double gcRate, long usedSizeInBytes, long maxSizeInBytes, long eventCount) { + this.gcRate = gcRate; + if (maxSizeInBytes > 0) { + this.usedPercent = NumberUtil.percentOf(usedSizeInBytes, maxSizeInBytes); + } else { + this.usedPercent = 0; } + this.maxSizeInBytes = maxSizeInBytes; + this.eventCount = eventCount; + } - if (events.size() < 2 || lastCount == 0) { - return 0; + static GarbageCollectionStats forHeap(Collection events) { + if (events.isEmpty()) { + return noData(); } else { - long elapsed = lastGC - firstGC; - long totalCount = lastCount - firstCount; - return ((double) totalCount) / elapsed * 1000; + return new GarbageCollectionStats( + calculateRate(events), + calculateAverageUsage(events), + findMaxSize(events), + events.size() + ); } } - static long calculateAverageUsage(Set events) { - if (events.size() < 1) { - return -1; + static GarbageCollectionStats forNonHeap(Collection events) { + if (events.isEmpty()) { + return noData(); + } else { + return new GarbageCollectionStats( + 0, // non-heap spaces are not garbage collected + calculateAverageUsage(events), + findMaxSize(events), + events.size() + ); } + } - long total = 0; - long firstCount = 0; - long lastCount = 0; - for (GarbageCollectionEvent event : events) { - // Skip if the garbage collector did not fire in between events - if (event.getCount() == lastCount || event.getCount() == 0) { - continue; - } - - MemoryUsage usage = event.getUsage(); - if (firstCount == 0) { - firstCount = event.getCount(); - total += usage.getUsed(); - } else { - total += usage.getUsed() * (event.getCount() - lastCount); - } - - lastCount = event.getCount(); - } + private static GarbageCollectionStats noData() { + return new GarbageCollectionStats(0, 0, -1, 0); + } - if (lastCount == 0 || lastCount == firstCount) { - return -1; - } else { - long totalCount = lastCount - firstCount + 1; - return total / totalCount; + private static double calculateRate(Collection events) { + if (events.size() < 2) { + // not enough data points + return 0; } + GarbageCollectionEvent first = events.iterator().next(); + GarbageCollectionEvent last = Iterables.getLast(events); + // Total number of garbage collection events observed in the window + long gcCountDelta = last.getCount() - first.getCount(); + // Time interval between the first event in the window and the last + long timeDelta = TimeUnit.MILLISECONDS.toSeconds(last.getTimestamp() - first.getTimestamp()); + return (double)gcCountDelta / timeDelta; } - static long calculateMaxSize(Set events) { - if (events.size() < 1) { - return -1; + private static long calculateAverageUsage(Collection events) { + long sum = 0; + for (GarbageCollectionEvent event : events) { + sum += event.getUsage().getUsed(); } - - // Maximum pool size is fixed, so we should only need to get it from the first event - MemoryUsage usage = events.iterator().next().getUsage(); - return usage.getMax(); + return sum / events.size(); } - public double getRate() { - return rate; + private static long findMaxSize(Collection events) { + // Maximum pool size is fixed, so we should only need to get it from the first event + GarbageCollectionEvent first = events.iterator().next(); + return first.getUsage().getMax(); } - public int getUsage() { - if (used > 0 && max > 0) { - return NumberUtil.percentOf(used, max); - } else { - return -1; - } + public double getGcRate() { + return gcRate; } - public double getUsed() { - return used; + public int getUsedPercent() { + return usedPercent; } - public long getMax() { - return max; + public long getMaxSizeInBytes() { + return maxSizeInBytes; } - public long getEventCount() { - return eventCount; + public boolean isValid() { + return eventCount >= 5 && maxSizeInBytes > 0; } } diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectorMonitoringStrategy.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectorMonitoringStrategy.java index 9d8453a71a34a..ebce9c8318773 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectorMonitoringStrategy.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/GarbageCollectorMonitoringStrategy.java @@ -16,34 +16,49 @@ package org.gradle.launcher.daemon.server.health.gc; +import org.gradle.api.Transformer; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.api.specs.Spec; +import org.gradle.internal.jvm.Jvm; +import org.gradle.util.CollectionUtils; + +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.util.List; + public enum GarbageCollectorMonitoringStrategy { - ORACLE_PARALLEL_CMS("PS Old Gen", "PS Perm Gen", "PS MarkSweep", 1.2, 80, 80, 5.0), - ORACLE_6_CMS("CMS Old Gen", "CMS Perm Gen", "ConcurrentMarkSweep", 1.2, 80, 80, 5.0), - ORACLE_SERIAL("Tenured Gen", "Perm Gen", "MarkSweepCompact", 1.2, 80, 80, 5.0), - ORACLE_G1("G1 Old Gen", "G1 Perm Gen", "G1 Old Generation", 0.4, 75, 80, 2.0), - IBM_ALL("Java heap", "PermGen Not Used", "MarkSweepCompact", 0.8, 70, -1, 6.0), + + ORACLE_PARALLEL_CMS("PS Old Gen", "Metaspace", "PS MarkSweep", 1.2, 80, 80, 5.0), + ORACLE_6_CMS("CMS Old Gen", "Metaspace", "ConcurrentMarkSweep", 1.2, 80, 80, 5.0), + ORACLE_SERIAL("Tenured Gen", "Metaspace", "MarkSweepCompact", 1.2, 80, 80, 5.0), + ORACLE_G1("G1 Old Gen", "Metaspace", "G1 Old Generation", 0.4, 75, 80, 2.0), + IBM_ALL("Java heap", "Not Used", "MarkSweepCompact", 0.8, 70, -1, 6.0), UNKNOWN(null, null, null, -1, -1, -1, -1); - private final String tenuredPoolName; - private final String permGenPoolName; + private static final Logger LOGGER = Logging.getLogger(GarbageCollectionMonitor.class); + + private final String heapPoolName; + private final String nonHeapPoolName; private final String garbageCollectorName; private final double gcRateThreshold; - private final int tenuredUsageThreshold; - private final int permGenUsageThreshold; + private final int heapUsageThreshold; + private final int nonHeapUsageThreshold; private final double thrashingThreshold; - GarbageCollectorMonitoringStrategy(String tenuredPoolName, String permGenPoolName, String garbageCollectorName, double gcRateThreshold, int tenuredUsageThreshold, int permGenUsageThreshold, double thrashingThreshold) { - this.tenuredPoolName = tenuredPoolName; - this.permGenPoolName = permGenPoolName; + GarbageCollectorMonitoringStrategy(String heapPoolName, String nonHeapPoolName, String garbageCollectorName, double gcRateThreshold, int heapUsageThreshold, int nonHeapUsageThreshold, double thrashingThreshold) { + this.heapPoolName = heapPoolName; + this.nonHeapPoolName = nonHeapPoolName; this.garbageCollectorName = garbageCollectorName; this.gcRateThreshold = gcRateThreshold; - this.tenuredUsageThreshold = tenuredUsageThreshold; - this.permGenUsageThreshold = permGenUsageThreshold; + this.heapUsageThreshold = heapUsageThreshold; + this.nonHeapUsageThreshold = nonHeapUsageThreshold; this.thrashingThreshold = thrashingThreshold; } - public String getTenuredPoolName() { - return tenuredPoolName; + public String getHeapPoolName() { + return heapPoolName; } public String getGarbageCollectorName() { @@ -54,19 +69,51 @@ public double getGcRateThreshold() { return gcRateThreshold; } - public int getTenuredUsageThreshold() { - return tenuredUsageThreshold; + public int getHeapUsageThreshold() { + return heapUsageThreshold; } - public String getPermGenPoolName() { - return permGenPoolName; + public String getNonHeapPoolName() { + return nonHeapPoolName; } - public int getPermGenUsageThreshold() { - return permGenUsageThreshold; + public int getNonHeapUsageThreshold() { + return nonHeapUsageThreshold; } public double getThrashingThreshold() { return thrashingThreshold; } + + public static GarbageCollectorMonitoringStrategy determineGcStrategy() { + final List garbageCollectors = CollectionUtils.collect(ManagementFactory.getGarbageCollectorMXBeans(), new Transformer() { + @Override + public String transform(GarbageCollectorMXBean garbageCollectorMXBean) { + return garbageCollectorMXBean.getName(); + } + }); + GarbageCollectorMonitoringStrategy gcStrategy = CollectionUtils.findFirst(GarbageCollectorMonitoringStrategy.values(), new Spec() { + @Override + public boolean isSatisfiedBy(GarbageCollectorMonitoringStrategy strategy) { + return garbageCollectors.contains(strategy.getGarbageCollectorName()); + } + }); + + if (gcStrategy == null) { + LOGGER.info("Unable to determine a garbage collection monitoring strategy for " + Jvm.current().toString()); + return GarbageCollectorMonitoringStrategy.UNKNOWN; + } else { + List memoryPools = CollectionUtils.collect(ManagementFactory.getMemoryPoolMXBeans(), new Transformer() { + @Override + public String transform(MemoryPoolMXBean memoryPoolMXBean) { + return memoryPoolMXBean.getName(); + } + }); + if (!memoryPools.contains(gcStrategy.heapPoolName) || !memoryPools.contains(gcStrategy.nonHeapPoolName)) { + LOGGER.info("Unable to determine which memory pools to monitor for " + Jvm.current().toString()); + return GarbageCollectorMonitoringStrategy.UNKNOWN; + } + return gcStrategy; + } + } } diff --git a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/SlidingWindow.java b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/SlidingWindow.java index 2b2f2c8f3040f..1f09fa5643236 100644 --- a/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/SlidingWindow.java +++ b/subprojects/launcher/src/main/java/org/gradle/launcher/daemon/server/health/gc/SlidingWindow.java @@ -16,7 +16,7 @@ package org.gradle.launcher.daemon.server.health.gc; -import java.util.Set; +import java.util.Collection; public interface SlidingWindow { /** @@ -24,12 +24,17 @@ public interface SlidingWindow { * * @param element The element to add */ - public void slideAndInsert(T element); + void slideAndInsert(T element); /** - * Returns a snapshot of the elements in the window as a Set view. + * Returns a snapshot of the elements in the window. * - * @return Set view of the elements + * @return collection view of the elements */ - public Set snapshot(); + Collection snapshot(); + + /** + * @return the latest event in the window. + */ + T latest(); } diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ContinuousBuildActionExecuter.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ContinuousBuildActionExecuter.java index 56f60a4b2ff22..0d246d4aad43b 100644 --- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ContinuousBuildActionExecuter.java +++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/ContinuousBuildActionExecuter.java @@ -105,6 +105,7 @@ private void waitForDeployments(BuildAction action, BuildRequestContext requestC ((DeploymentInternal) deployment).outOfDate(); } logger.println().println("Reloadable deployment detected. Entering continuous build."); + resetBuildStartedTime(buildSessionScopeServices); ContinuousExecutionGate deploymentRequestExecutionGate = deploymentRegistry.getExecutionGate(); executeMultipleBuilds(action, requestContext, actionParameters, buildSessionScopeServices, cancellableOperationManager, deploymentRequestExecutionGate); } @@ -114,8 +115,6 @@ private void waitForDeployments(BuildAction action, BuildRequestContext requestC private BuildActionResult executeMultipleBuilds(BuildAction action, final BuildRequestContext requestContext, final BuildActionParameters actionParameters, final ServiceRegistry buildSessionScopeServices, CancellableOperationManager cancellableOperationManager, ContinuousExecutionGate continuousExecutionGate) { BuildCancellationToken cancellationToken = requestContext.getCancellationToken(); - BuildStartedTime buildStartedTime = buildSessionScopeServices.get(BuildStartedTime.class); - Clock clock = buildSessionScopeServices.get(Clock.class); BuildActionResult lastResult; while (true) { @@ -152,7 +151,7 @@ public void run() { break; } else { logger.println("Change detected, executing build...").println(); - buildStartedTime.reset(clock.getCurrentTime()); + resetBuildStartedTime(buildSessionScopeServices); } } @@ -160,6 +159,12 @@ public void run() { return lastResult; } + private void resetBuildStartedTime(ServiceRegistry buildSessionScopeServices) { + BuildStartedTime buildStartedTime = buildSessionScopeServices.get(BuildStartedTime.class); + Clock clock = buildSessionScopeServices.get(Clock.class); + buildStartedTime.reset(clock.getCurrentTime()); + } + private String determineExitHint(BuildRequestContext requestContext) { if (requestContext.isInteractive()) { if (operatingSystem.isWindows()) { diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClassLoaderCache.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClassLoaderCache.java index 9726b918e49b1..e3859446a2dcb 100644 --- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClassLoaderCache.java +++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClassLoaderCache.java @@ -18,7 +18,7 @@ import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Transformer; import org.gradle.internal.classloader.ClassLoaderUtils; diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClasspathInferer.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClasspathInferer.java index 5a39110ec5f79..446a4cabb817a 100644 --- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClasspathInferer.java +++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClasspathInferer.java @@ -18,7 +18,7 @@ import com.google.common.collect.MapMaker; import com.google.common.io.ByteStreams; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.GradleException; import org.gradle.internal.classloader.ClassLoaderUtils; import org.gradle.internal.classloader.ClasspathUtil; diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClientSidePayloadClassLoaderRegistry.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClientSidePayloadClassLoaderRegistry.java index ed81ea59cf28c..de6d5ccea6632 100644 --- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClientSidePayloadClassLoaderRegistry.java +++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/ClientSidePayloadClassLoaderRegistry.java @@ -17,7 +17,7 @@ package org.gradle.tooling.internal.provider.serialization; import com.google.common.collect.Sets; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import java.lang.ref.WeakReference; import java.net.URL; diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/DefaultPayloadClassLoaderRegistry.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/DefaultPayloadClassLoaderRegistry.java index 637ca3eaf6cfa..d481aebff9ce8 100644 --- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/DefaultPayloadClassLoaderRegistry.java +++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/DefaultPayloadClassLoaderRegistry.java @@ -18,7 +18,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Transformer; import org.gradle.internal.classloader.ClassLoaderSpec; import org.gradle.internal.classloader.ClassLoaderVisitor; diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/PayloadClassLoaderRegistry.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/PayloadClassLoaderRegistry.java index 2cdb3d8d4da36..c91bda53f754a 100644 --- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/PayloadClassLoaderRegistry.java +++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/PayloadClassLoaderRegistry.java @@ -16,7 +16,7 @@ package org.gradle.tooling.internal.provider.serialization; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; /** *

    Implementations must allow concurrent sessions. diff --git a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/PayloadSerializer.java b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/PayloadSerializer.java index cab1e79cf11a5..8401c537787e0 100644 --- a/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/PayloadSerializer.java +++ b/subprojects/launcher/src/main/java/org/gradle/tooling/internal/provider/serialization/PayloadSerializer.java @@ -16,7 +16,7 @@ package org.gradle.tooling.internal.provider.serialization; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.IoActions; import org.gradle.internal.UncheckedException; import org.gradle.internal.io.StreamByteBuffer; diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/DaemonHealthStatsTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/DaemonHealthStatsTest.groovy index 03626460557ae..402fbd0994319 100644 --- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/DaemonHealthStatsTest.groovy +++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/DaemonHealthStatsTest.groovy @@ -18,7 +18,7 @@ package org.gradle.launcher.daemon.server.health import org.gradle.internal.event.DefaultListenerManager import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionInfo -import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionMonitor +import org.gradle.launcher.daemon.server.health.gc.DefaultGarbageCollectionMonitor import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionStats import org.gradle.launcher.daemon.server.stats.DaemonRunningStats import spock.lang.Specification @@ -27,7 +27,7 @@ class DaemonHealthStatsTest extends Specification { def listenerManager = new DefaultListenerManager() def gcInfo = Stub(GarbageCollectionInfo) - def gcMonitor = Stub(GarbageCollectionMonitor) + def gcMonitor = Stub(DefaultGarbageCollectionMonitor) def runningStats = Stub(DaemonRunningStats) def healthStats = new DaemonHealthStats(runningStats, gcInfo, gcMonitor) @@ -42,19 +42,18 @@ class DaemonHealthStatsTest extends Specification { def "consumes subsequent builds"() { when: gcInfo.getCollectionTime() >> 25 - gcMonitor.getTenuredStats() >> { - Stub(GarbageCollectionStats) { - getUsage() >> 10 - getMax() >> 1024 - getRate() >> 1.0 - } + gcMonitor.getHeapStats() >> { + new GarbageCollectionStats(1.0, 103, 1024, 5) + } + gcMonitor.getNonHeapStats() >> { + new GarbageCollectionStats(0, 1024, 2048, 5) } runningStats.getBuildCount() >> 1 runningStats.getPrettyUpTime() >> "3 mins" runningStats.getAllBuildsTime() >> 1000 then: - healthStats.healthInfo == String.format("Starting 2nd build in daemon [uptime: 3 mins, performance: 98%%, GC rate: %.2f/s, tenured heap usage: 10%% of %.1f kB]", 1.0, 1.0) + healthStats.healthInfo == "Starting 2nd build in daemon [uptime: 3 mins, performance: 98%, GC rate: 1.00/s, heap usage: 10% of 1.0 kB, non-heap usage: 50% of 2.0 kB]" } def "handles no garbage collection data"() { @@ -64,16 +63,15 @@ class DaemonHealthStatsTest extends Specification { runningStats.getPrettyUpTime() >> "3 mins" runningStats.getAllBuildsTime() >> 1000 - gcMonitor.getTenuredStats() >> { - Stub(GarbageCollectionStats) { - getUsage() >> -1 - getMax() >> -1 - getRate() >> 0 - } + gcMonitor.getHeapStats() >> { + GarbageCollectionStats.noData() + } + gcMonitor.getNonHeapStats() >> { + GarbageCollectionStats.noData() } then: - healthStats.healthInfo == "Starting 2nd build in daemon [uptime: 3 mins, performance: 98%, no major garbage collections]" + healthStats.healthInfo == "Starting 2nd build in daemon [uptime: 3 mins, performance: 98%]" } } diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/DaemonMemoryStatusTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/DaemonMemoryStatusTest.groovy index 7d5978152cd7c..aea4fba63d870 100644 --- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/DaemonMemoryStatusTest.groovy +++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/DaemonMemoryStatusTest.groovy @@ -16,155 +16,110 @@ package org.gradle.launcher.daemon.server.health -import org.gradle.api.GradleException -import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionMonitor import org.gradle.launcher.daemon.server.health.gc.GarbageCollectionStats -import org.gradle.launcher.daemon.server.health.gc.GarbageCollectorMonitoringStrategy import org.gradle.util.SetSystemProperties import org.junit.Rule import spock.lang.Specification import spock.lang.Unroll -import static DaemonMemoryStatus.PERMGEN_USAGE_EXPIRE_AT -import static DaemonMemoryStatus.TENURED_RATE_EXPIRE_AT -import static DaemonMemoryStatus.TENURED_USAGE_EXPIRE_AT -import static DaemonMemoryStatus.THRASHING_EXPIRE_AT class DaemonMemoryStatusTest extends Specification { - @Rule SetSystemProperties props = new SetSystemProperties() + @Rule + SetSystemProperties props = new SetSystemProperties() - def gcMonitor = Mock(GarbageCollectionMonitor) def stats = Mock(DaemonHealthStats) - - def "validates supplied tenured usage threshold value"() { - System.setProperty(TENURED_USAGE_EXPIRE_AT, "foo") - - when: - status.isTenuredSpaceExhausted() - - then: - def ex = thrown(GradleException) - ex.message == "System property 'org.gradle.daemon.performance.tenured-usage-expire-at' has incorrect value: 'foo'. The value needs to be an integer." - } - - def "validates supplied tenured rate threshold value"() { - System.setProperty(TENURED_RATE_EXPIRE_AT, "foo") - - when: - status.isTenuredSpaceExhausted() - - then: - def ex = thrown(GradleException) - ex.message == "System property 'org.gradle.daemon.performance.tenured-rate-expire-at' has incorrect value: 'foo'. The value needs to be a double." + DaemonMemoryStatus create(int heapUsageThreshold, double heapRateThreshold, int nonHeapUsageThreshold, double thrashingThreshold) { + return new DaemonMemoryStatus(stats, heapUsageThreshold, heapRateThreshold, nonHeapUsageThreshold, thrashingThreshold) } @Unroll - def "knows when tenured space is exhausted (#rateThreshold <= #rate, #usageThreshold <= #used)"() { + def "knows when heap space is exhausted (#rateThreshold <= #rate, #usageThreshold <= #usage)"(double rateThreshold, int usageThreshold, double rate, int usage, boolean unhealthy) { when: - System.setProperty(TENURED_USAGE_EXPIRE_AT, usageThreshold.toString()) - System.setProperty(TENURED_RATE_EXPIRE_AT, rateThreshold.toString()) - gcMonitor.getTenuredStats() >> { - Stub(GarbageCollectionStats) { - getUsage() >> used - getRate() >> rate - getEventCount() >> 10 - } + def status = create(usageThreshold, rateThreshold, 100, 100) + stats.getHeapStats() >> { + new GarbageCollectionStats(rate, usage, 100, 10) } then: - status.isTenuredSpaceExhausted() == unhealthy + status.isHeapSpaceExhausted() == unhealthy where: - rateThreshold | usageThreshold | rate | used | unhealthy - 1.0 | 90 | 1.1 | 100 | true - 1.0 | 90 | 1.1 | 91 | true - 1.0 | 90 | 1.1 | 89 | false - 1.0 | 90 | 0.9 | 91 | false - 1.0 | 0 | 1.0 | 0 | false - 1.0 | 0 | 1.0 | -1 | false - 0 | 90 | 0 | 100 | false - 1.0 | 0 | 1.1 | 100 | false - 0 | 90 | 1.1 | 100 | false - 1.0 | 100 | 1.1 | 100 | true - 1.0 | 75 | 1.1 | 75 | true - 1.0 | 75 | 1.0 | 100 | true + rateThreshold | usageThreshold | rate | usage | unhealthy + 1.0 | 90 | 1.1 | 100 | true + 1.0 | 90 | 1.1 | 91 | true + 1.0 | 90 | 1.1 | 89 | false + 1.0 | 90 | 0.9 | 91 | false + 1.0 | 0 | 1.0 | 0 | false + 0 | 90 | 0 | 100 | false + 1.0 | 0 | 1.1 | 100 | false + 0 | 90 | 1.1 | 100 | false + 1.0 | 100 | 1.1 | 100 | true + 1.0 | 75 | 1.1 | 75 | true + 1.0 | 75 | 1.0 | 100 | true } @Unroll - def "knows when perm gen space is exhausted (#usageThreshold <= #used, #usageThreshold <= #used)"() { + def "knows when metaspace is exhausted (#usageThreshold <= #usage, #usageThreshold <= #usage)"() { when: - System.setProperty(PERMGEN_USAGE_EXPIRE_AT, usageThreshold.toString()) - gcMonitor.getPermGenStats() >> { - Stub(GarbageCollectionStats) { - getUsage() >> used - getEventCount() >> 10 - } + def status = create(100, 100, usageThreshold, 100) + stats.getNonHeapStats() >> { + new GarbageCollectionStats(0, usage, 100, 10) } then: - status.isPermGenSpaceExhausted() == unhealthy + status.isNonHeapSpaceExhausted() == unhealthy where: - usageThreshold | used | unhealthy - 90 | 100 | true - 90 | 91 | true - 90 | 90 | true - 90 | 89 | false - 0 | 0 | false - 0 | 100 | false - 100 | 100 | true + usageThreshold | usage | unhealthy + 90 | 100 | true + 90 | 91 | true + 90 | 90 | true + 90 | 89 | false + 0 | 0 | false + 0 | 100 | false + 100 | 100 | true } @Unroll - def "knows when gc is thrashing (#rateThreshold <= #rate)"() { + def "knows when gc is thrashing (#rateThreshold <= #rate)"(double rateThreshold, int usageThreshold, double rate, int usage, boolean thrashing) { when: - System.setProperty(TENURED_USAGE_EXPIRE_AT, usageThreshold.toString()) - System.setProperty(THRASHING_EXPIRE_AT, rateThreshold.toString()) - gcMonitor.getTenuredStats() >> { - Stub(GarbageCollectionStats) { - getRate() >> rate - getUsage() >> usage - getEventCount() >> 10 - } + def status = create(usageThreshold, 100, 100, rateThreshold) + stats.getHeapStats() >> { + new GarbageCollectionStats(rate, usage, 100, 10) } then: status.isThrashing() == thrashing where: - rateThreshold | usageThreshold | rate | usage | thrashing - 10 | 90 | 15 | 100 | true - 10 | 90 | 10.1 | 91 | true - 10 | 90 | 10.1 | 89 | false - 10 | 90 | 9.9 | 91 | false - 10 | 0 | 15 | 0 | false - 0 | 90 | 0 | 100 | false - 10 | 0 | 10.1 | 100 | false - 0 | 90 | 10.1 | 100 | false - 10 | 100 | 10.1 | 100 | true - 10 | 75 | 10.1 | 75 | true - 10 | 75 | 10 | 100 | true - 10 | 90 | 0 | 100 | false - 10 | 90 | 15 | 0 | false + rateThreshold | usageThreshold | rate | usage | thrashing + 10 | 90 | 15 | 100 | true + 10 | 90 | 10.1 | 91 | true + 10 | 90 | 10.1 | 89 | false + 10 | 90 | 9.9 | 91 | false + 10 | 0 | 15 | 0 | false + 0 | 90 | 0 | 100 | false + 10 | 0 | 10.1 | 100 | false + 0 | 90 | 10.1 | 100 | false + 10 | 100 | 10.1 | 100 | true + 10 | 75 | 10.1 | 75 | true + 10 | 75 | 10 | 100 | true + 10 | 90 | 0 | 100 | false + 10 | 90 | 15 | 0 | false } def "can disable daemon performance monitoring"() { when: + def status = create(100, 100, 100, 100) System.setProperty(DaemonMemoryStatus.ENABLE_PERFORMANCE_MONITORING, "false") then: - !status.isTenuredSpaceExhausted() + !status.isHeapSpaceExhausted() and: - !status.isPermGenSpaceExhausted() + !status.isNonHeapSpaceExhausted() and: !status.isThrashing() } - - DaemonMemoryStatus getStatus() { - 1 * gcMonitor.gcStrategy >> GarbageCollectorMonitoringStrategy.ORACLE_PARALLEL_CMS - _ * stats.getGcMonitor() >> gcMonitor - return new DaemonMemoryStatus(stats) - } } diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowTenuredSpaceDaemonExpirationStrategyTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowHeapSpaceDaemonExpirationStrategyTest.groovy similarity index 74% rename from subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowTenuredSpaceDaemonExpirationStrategyTest.groovy rename to subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowHeapSpaceDaemonExpirationStrategyTest.groovy index bacd4684a2e45..9f07beb337198 100644 --- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowTenuredSpaceDaemonExpirationStrategyTest.groovy +++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowHeapSpaceDaemonExpirationStrategyTest.groovy @@ -21,31 +21,31 @@ import spock.lang.Specification import static org.gradle.launcher.daemon.server.expiry.DaemonExpirationStatus.GRACEFUL_EXPIRE -class LowTenuredSpaceDaemonExpirationStrategyTest extends Specification { +class LowHeapSpaceDaemonExpirationStrategyTest extends Specification { private final DaemonMemoryStatus status = Mock(DaemonMemoryStatus) def "daemon is expired when tenured space is low" () { - LowTenuredSpaceDaemonExpirationStrategy strategy = new LowTenuredSpaceDaemonExpirationStrategy(status) + LowHeapSpaceDaemonExpirationStrategy strategy = new LowHeapSpaceDaemonExpirationStrategy(status) when: DaemonExpirationResult result = strategy.checkExpiration() then: - 1 * status.isTenuredSpaceExhausted() >> true + 1 * status.isHeapSpaceExhausted() >> true and: result.status == GRACEFUL_EXPIRE - result.reason == LowTenuredSpaceDaemonExpirationStrategy.EXPIRATION_REASON + result.reason == LowHeapSpaceDaemonExpirationStrategy.EXPIRATION_REASON } def "daemon is not expired when tenured space is fine" () { - LowTenuredSpaceDaemonExpirationStrategy strategy = new LowTenuredSpaceDaemonExpirationStrategy(status) + LowHeapSpaceDaemonExpirationStrategy strategy = new LowHeapSpaceDaemonExpirationStrategy(status) when: DaemonExpirationResult result = strategy.checkExpiration() then: - 1 * status.isTenuredSpaceExhausted() >> false + 1 * status.isHeapSpaceExhausted() >> false and: result == DaemonExpirationResult.NOT_TRIGGERED diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowPermGenDaemonExpirationStrategyTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowNonHeapDaemonExpirationStrategyTest.groovy similarity index 76% rename from subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowPermGenDaemonExpirationStrategyTest.groovy rename to subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowNonHeapDaemonExpirationStrategyTest.groovy index 4c7cc5754cfa4..10f4156aa7aaf 100644 --- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowPermGenDaemonExpirationStrategyTest.groovy +++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/LowNonHeapDaemonExpirationStrategyTest.groovy @@ -21,31 +21,31 @@ import spock.lang.Specification import static org.gradle.launcher.daemon.server.expiry.DaemonExpirationStatus.GRACEFUL_EXPIRE -class LowPermGenDaemonExpirationStrategyTest extends Specification { +class LowNonHeapDaemonExpirationStrategyTest extends Specification { private final DaemonMemoryStatus status = Mock(DaemonMemoryStatus) def "daemon is expired when perm gen space is low" () { - LowPermGenDaemonExpirationStrategy strategy = new LowPermGenDaemonExpirationStrategy(status) + LowNonHeapDaemonExpirationStrategy strategy = new LowNonHeapDaemonExpirationStrategy(status) when: DaemonExpirationResult result = strategy.checkExpiration() then: - 1 * status.isPermGenSpaceExhausted() >> true + 1 * status.isNonHeapSpaceExhausted() >> true and: result.status == GRACEFUL_EXPIRE - result.reason == LowPermGenDaemonExpirationStrategy.EXPIRATION_REASON + result.reason == LowNonHeapDaemonExpirationStrategy.EXPIRATION_REASON } def "daemon is not expired when tenured space is fine" () { - LowPermGenDaemonExpirationStrategy strategy = new LowPermGenDaemonExpirationStrategy(status) + LowNonHeapDaemonExpirationStrategy strategy = new LowNonHeapDaemonExpirationStrategy(status) when: DaemonExpirationResult result = strategy.checkExpiration() then: - 1 * status.isPermGenSpaceExhausted() >> false + 1 * status.isNonHeapSpaceExhausted() >> false and: result == DaemonExpirationResult.NOT_TRIGGERED diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitorTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitorTest.groovy index 6e14e5073ea9e..6c4c8b9b1966c 100644 --- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitorTest.groovy +++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionMonitorTest.groovy @@ -17,56 +17,37 @@ package org.gradle.launcher.daemon.server.health.gc import spock.lang.Specification -import spock.lang.Unroll import java.util.concurrent.ScheduledExecutorService class GarbageCollectionMonitorTest extends Specification { ScheduledExecutorService scheduledExecutorService = Mock(ScheduledExecutorService) - @Unroll - def "schedules periodic garbage collection checks (#strategy)"() { - when: - new GarbageCollectionMonitor(strategy, scheduledExecutorService) - - then: - 1 * scheduledExecutorService.scheduleAtFixedRate(_, _, _, _) >> { args -> - GarbageCollectionCheck check = args[0] as GarbageCollectionCheck - assert check.garbageCollector == strategy.garbageCollectorName - assert check.memoryPools == [ strategy.tenuredPoolName, strategy.permGenPoolName ] - assert check.events.containsKey(strategy.tenuredPoolName) - assert check.events.containsKey(strategy.permGenPoolName) - } - - where: - strategy << GarbageCollectorMonitoringStrategy.values() - GarbageCollectorMonitoringStrategy.UNKNOWN - } - def "does not schedule garbage collection check when GC strategy is unknown" () { when: - new GarbageCollectionMonitor(GarbageCollectorMonitoringStrategy.UNKNOWN, scheduledExecutorService) + new DefaultGarbageCollectionMonitor(GarbageCollectorMonitoringStrategy.UNKNOWN, scheduledExecutorService) then: 0 * scheduledExecutorService.scheduleAtFixedRate(_, _, _, _) } - def "tenured stats defaults to empty given unknown garbage collector"() { + def "heap stats defaults to empty given unknown garbage collector"() { given: - def monitor = new GarbageCollectionMonitor(GarbageCollectorMonitoringStrategy.UNKNOWN, scheduledExecutorService) + def monitor = new DefaultGarbageCollectionMonitor(GarbageCollectorMonitoringStrategy.UNKNOWN, scheduledExecutorService) when: - monitor.getTenuredStats() + monitor.getHeapStats() then: noExceptionThrown() } - def "perm gen stats defaults to empty given unknown garbage collector"() { + def "non-heap stats defaults to empty given unknown garbage collector"() { given: - def monitor = new GarbageCollectionMonitor(GarbageCollectorMonitoringStrategy.UNKNOWN, scheduledExecutorService) + def monitor = new DefaultGarbageCollectionMonitor(GarbageCollectorMonitoringStrategy.UNKNOWN, scheduledExecutorService) when: - monitor.getPermGenStats() + monitor.getNonHeapStats() then: noExceptionThrown() diff --git a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionStatsTest.groovy b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionStatsTest.groovy index 2e7deedf71e0f..a9c8d4f0a7dab 100644 --- a/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionStatsTest.groovy +++ b/subprojects/launcher/src/test/groovy/org/gradle/launcher/daemon/server/health/gc/GarbageCollectionStatsTest.groovy @@ -20,25 +20,66 @@ import spock.lang.Specification import java.lang.management.MemoryUsage; -public class GarbageCollectionStatsTest extends Specification { - def "correctly calculates garbage collection rate"() { +class GarbageCollectionStatsTest extends Specification { + def "correctly calculates stats for heaps"() { + def heapStats = GarbageCollectionStats.forHeap(heapEvents) expect: - new GarbageCollectionStats(checkStream).rate == (double)8/3 + heapStats.valid + heapStats.gcRate == 3.0d + heapStats.maxSizeInBytes == 1000 + heapStats.usedPercent == 50 } - def "correctly calculates average usage"() { + def "correctly calculates stats for non-heaps"() { + def nonHeapStats = GarbageCollectionStats.forNonHeap(nonHeapEvents) expect: - new GarbageCollectionStats(checkStream).usage == 73 + nonHeapStats.valid + nonHeapStats.gcRate == 0 + nonHeapStats.maxSizeInBytes == 1000 + nonHeapStats.usedPercent == 70 } - Set getCheckStream() { - Set checks = [ - new GarbageCollectionEvent(1000, new MemoryUsage(0, 250, 1000, 1000), 2), - new GarbageCollectionEvent(2000, new MemoryUsage(0, 500, 1000, 1000), 3), - new GarbageCollectionEvent(2500, new MemoryUsage(0, 500, 1000, 1000), 3), - new GarbageCollectionEvent(3000, new MemoryUsage(0, 750, 1000, 1000), 6), - new GarbageCollectionEvent(3500, new MemoryUsage(0, 750, 1000, 1000), 6), - new GarbageCollectionEvent(4000, new MemoryUsage(0, 900, 1000, 1000), 10) + def "reports invalid when fewer than 5 events are seen"() { + expect: + !stats.valid + stats.maxSizeInBytes == 1000 + where: + stats << [ + GarbageCollectionStats.forHeap(heapEvents.take(3)), + GarbageCollectionStats.forNonHeap(nonHeapEvents.take(3)) + ] + } + + def "reports invalid when no events are seen"() { + expect: + !stats.valid + stats.gcRate == 0 + stats.maxSizeInBytes == -1 + stats.usedPercent == 0 + where: + stats << [ + GarbageCollectionStats.forHeap([]), + GarbageCollectionStats.forNonHeap([]) + ] + } + + def getHeapEvents() { + return [ + new GarbageCollectionEvent(0000, new MemoryUsage(0, 100, 1000, 1000), 1), + new GarbageCollectionEvent(1000, new MemoryUsage(0, 250, 1000, 1000), 2), + new GarbageCollectionEvent(2000, new MemoryUsage(0, 500, 1000, 1000), 3), + new GarbageCollectionEvent(3000, new MemoryUsage(0, 750, 1000, 1000), 6), + new GarbageCollectionEvent(4000, new MemoryUsage(0, 900, 1000, 1000), 13) + ] + } + + def getNonHeapEvents() { + return [ + new GarbageCollectionEvent(0000, new MemoryUsage(0, 500, 1000, 1000), 0), + new GarbageCollectionEvent(1000, new MemoryUsage(0, 600, 1000, 1000), 0), + new GarbageCollectionEvent(2000, new MemoryUsage(0, 700, 1000, 1000), 0), + new GarbageCollectionEvent(3000, new MemoryUsage(0, 800, 1000, 1000), 0), + new GarbageCollectionEvent(4000, new MemoryUsage(0, 900, 1000, 1000), 0) ] } } diff --git a/subprojects/logging/logging.gradle.kts b/subprojects/logging/logging.gradle.kts index 734770fd3ff08..ed99d53996c2b 100644 --- a/subprojects/logging/logging.gradle.kts +++ b/subprojects/logging/logging.gradle.kts @@ -21,7 +21,6 @@ dependencies { implementation(library("commons_lang")) implementation(library("guava")) implementation(library("jansi")) - implementation(library("jcip")) runtimeOnly(library("log4j_to_slf4j")) runtimeOnly(library("jcl_to_slf4j")) diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AbstractConsoleBuildPhaseFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AbstractConsoleBuildPhaseFunctionalTest.groovy index f2b2744569e53..3ee4297254ceb 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AbstractConsoleBuildPhaseFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AbstractConsoleBuildPhaseFunctionalTest.groovy @@ -16,32 +16,23 @@ package org.gradle.internal.logging.console -import org.gradle.api.logging.configuration.ConsoleOutput -import org.gradle.integtests.fixtures.AbstractIntegrationSpec -import org.gradle.integtests.fixtures.RichConsoleStyling -import org.gradle.integtests.fixtures.executer.ConsoleAttachment + +import org.gradle.integtests.fixtures.console.AbstractConsoleGroupedTaskFunctionalTest import org.gradle.integtests.fixtures.executer.GradleHandle import org.gradle.test.fixtures.ConcurrentTestUtil import org.gradle.test.fixtures.server.http.BlockingHttpServer import org.junit.Rule -import spock.lang.Unroll - -import static org.gradle.integtests.fixtures.FeaturePreviewsFixture.enableIncrementalArtifactTransformations -abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrationSpec implements RichConsoleStyling { +abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractConsoleGroupedTaskFunctionalTest { @Rule BlockingHttpServer server = new BlockingHttpServer() GradleHandle gradle def setup() { - executer.withConsole(consoleType) server.start() } - abstract ConsoleOutput getConsoleType() - - @Unroll - def "shows progress bar and percent phase completion when #attachment"() { + def "shows progress bar and percent phase completion"() { settingsFile << """ ${server.callFromBuild('settings')} include "a", "b", "c", "d" @@ -84,7 +75,6 @@ abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrati def task1 = server.expectAndBlock('task1') def task2 = server.expectAndBlock('task2') def buildFinished = server.expectAndBlock('build-finished') - executer.withTestConsoleAttached(attachment) gradle = executer.withTasks("hello2").start() expect: @@ -129,13 +119,9 @@ abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrati and: gradle.waitForFinish() - - where: - attachment << [ConsoleAttachment.ATTACHED, ConsoleAttachment.ATTACHED_STDOUT_ONLY] } - @Unroll - def "shows progress bar and percent phase completion with included build when #attachment"() { + def "shows progress bar and percent phase completion with included build"() { settingsFile << """ ${server.callFromBuild('settings')} includeBuild "child" @@ -177,7 +163,6 @@ abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrati def task1 = server.expectAndBlock('task1') def task2 = server.expectAndBlock('task2') def rootBuildFinished = server.expectAndBlock('root-build-finished') - executer.withTestConsoleAttached(attachment) gradle = executer.withTasks("hello2").start() expect: @@ -217,13 +202,9 @@ abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrati and: gradle.waitForFinish() - - where: - attachment << [ConsoleAttachment.ATTACHED, ConsoleAttachment.ATTACHED_STDOUT_ONLY] } - @Unroll - def "shows progress bar and percent phase completion with buildSrc build when #attachment"() { + def "shows progress bar and percent phase completion with buildSrc build"() { settingsFile << """ ${server.callFromBuild('settings')} """ @@ -267,7 +248,6 @@ abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrati def rootBuildScript = server.expectAndBlock('root-build-script') def task2 = server.expectAndBlock('task2') def rootBuildFinished = server.expectAndBlock('root-build-finished') - executer.withTestConsoleAttached(attachment) gradle = executer.withTasks("hello").start() expect: @@ -312,33 +292,25 @@ abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrati and: gradle.waitForFinish() - - where: - attachment << [ConsoleAttachment.ATTACHED, ConsoleAttachment.ATTACHED_STDOUT_ONLY] } - @Unroll - def "shows progress bar and percent phase completion with artifact transforms with #attachment"() { + def "shows progress bar and percent phase completion with artifact transforms"() { given: settingsFile << """ include 'lib' include 'util' """ - enableIncrementalArtifactTransformations(settingsFile) buildFile << """ def usage = Attribute.of('usage', String) def artifactType = Attribute.of('artifactType', String) - @AssociatedTransformAction(FileSizerAction) - interface FileSizer { - @Input - String getSuffix() - void setSuffix(String suffix) - } + abstract class FileSizer implements TransformAction { + interface Parameters extends TransformParameters { + @Input + String getSuffix() + void setSuffix(String suffix) + } - abstract class FileSizerAction implements TransformAction { - @TransformParameters - abstract FileSizer getParameters() @InputArtifactDependencies abstract FileCollection getDependencies() @InputArtifact @@ -425,7 +397,6 @@ abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrati def buildFinished = server.expectAndBlock('build-finished') when: - executer.withTestConsoleAttached(attachment) gradle = executer.withTasks(":util:resolve").start() then: @@ -455,23 +426,6 @@ abstract class AbstractConsoleBuildPhaseFunctionalTest extends AbstractIntegrati and: gradle.waitForFinish() - - where: - attachment << [ConsoleAttachment.ATTACHED, ConsoleAttachment.ATTACHED_STDOUT_ONLY] - } - - @Unroll - def "does not show progress bar when stdout is #attachment"() { - when: - succeeds() - - then: - result.assertNotOutput("INITIALIZING") - result.assertNotOutput("CONFIGURING") - result.assertNotOutput("EXECUTING") - - where: - attachment << [ConsoleAttachment.ATTACHED_STDERR_ONLY, ConsoleAttachment.NOT_ATTACHED] } void assertHasBuildPhase(String message) { diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AbstractConsoleConfigurationProgressFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AbstractConsoleConfigurationProgressFunctionalTest.groovy index 741c48b590dd3..438d3e3341f8b 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AbstractConsoleConfigurationProgressFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AbstractConsoleConfigurationProgressFunctionalTest.groovy @@ -156,7 +156,7 @@ abstract class AbstractConsoleConfigurationProgressFunctionalTest extends Abstra void assertHasWorkInProgress(String message) { ConcurrentTestUtil.poll { - assert gradle.standardOutput.contains(workInProgressLine("> " + message)) + assertHasWorkInProgress(gradle, "> " + message) } } } diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AutoConsoleBuildPhaseFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AutoConsoleBuildPhaseFunctionalTest.groovy new file mode 100644 index 0000000000000..41c9e492e52e1 --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/AutoConsoleBuildPhaseFunctionalTest.groovy @@ -0,0 +1,31 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.junit.Assume + + +class AutoConsoleBuildPhaseFunctionalTest extends AbstractConsoleBuildPhaseFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto + + @Override + def setup() { + // The dynamic content is written to stdout only when it is attached to a console + Assume.assumeTrue(consoleAttachment.stdoutAttached) + } +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/jvm/AbstractConsoleJvmTestWorkerFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/jvm/AbstractConsoleJvmTestWorkerFunctionalTest.groovy index c6e24c915f797..b2bc160ebfe98 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/jvm/AbstractConsoleJvmTestWorkerFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/jvm/AbstractConsoleJvmTestWorkerFunctionalTest.groovy @@ -60,8 +60,8 @@ abstract class AbstractConsoleJvmTestWorkerFunctionalTest extends AbstractIntegr then: ConcurrentTestUtil.poll { - assert containsTestExecutionWorkInProgressLine(gradleHandle, ':test', testClass1.renderedClassName) - assert containsTestExecutionWorkInProgressLine(gradleHandle, ':test', testClass2.renderedClassName) + containsTestExecutionWorkInProgressLine(gradleHandle, ':test', testClass1.renderedClassName) + containsTestExecutionWorkInProgressLine(gradleHandle, ':test', testClass2.renderedClassName) } testExecution.release(2) @@ -93,8 +93,8 @@ abstract class AbstractConsoleJvmTestWorkerFunctionalTest extends AbstractIntegr then: ConcurrentTestUtil.poll { - assert containsTestExecutionWorkInProgressLine(gradleHandle, ':project1:test', testClass1.renderedClassName) - assert containsTestExecutionWorkInProgressLine(gradleHandle, ':project2:test', testClass2.renderedClassName) + containsTestExecutionWorkInProgressLine(gradleHandle, ':project1:test', testClass1.renderedClassName) + containsTestExecutionWorkInProgressLine(gradleHandle, ':project2:test', testClass2.renderedClassName) } testExecution.release(2) diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/jvm/TestedProjectFixture.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/jvm/TestedProjectFixture.groovy index 5e9da72eb9376..55b8691f0184b 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/jvm/TestedProjectFixture.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/jvm/TestedProjectFixture.groovy @@ -62,8 +62,8 @@ class TestedProjectFixture implements RichConsoleStyling { """ } - static boolean containsTestExecutionWorkInProgressLine(GradleHandle gradleHandle, String taskPath, String testName) { - gradleHandle.standardOutput.contains(workInProgressLine("> $taskPath > Executing test $testName")) + static void containsTestExecutionWorkInProgressLine(GradleHandle gradleHandle, String taskPath, String testName) { + assertHasWorkInProgress(gradleHandle, "> $taskPath > Executing test $testName") } static class JavaTestClass { diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractBasicGroupedTaskLoggingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractBasicGroupedTaskLoggingFunctionalTest.groovy index 184c8009c3912..233eed53bbca4 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractBasicGroupedTaskLoggingFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractBasicGroupedTaskLoggingFunctionalTest.groovy @@ -53,7 +53,9 @@ abstract class AbstractBasicGroupedTaskLoggingFunctionalTest extends AbstractCon when: server.expectConcurrent("log1", "log2", "log3") - result = executer.withArgument("--parallel").withTasks("log").start().waitForFinish() + executer.withArgument("--parallel") + // run build in another process to avoid interference from logging from test fixtures + result = executer.withTasks("log").start().waitForFinish() then: result.groupedOutput.taskCount == 3 diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleBuildResultFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleBuildResultFunctionalTest.groovy index f462af6a43beb..2a7fc732eec6f 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleBuildResultFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleBuildResultFunctionalTest.groovy @@ -23,14 +23,8 @@ import org.gradle.integtests.fixtures.executer.LogContent import spock.lang.Unroll abstract class AbstractConsoleBuildResultFunctionalTest extends AbstractConsoleGroupedTaskFunctionalTest { - protected final String buildFailed = 'BUILD FAILED' - protected final String buildSuccess = 'BUILD SUCCESSFUL' - protected final StyledOutput buildFailedStyled = styled(buildFailed, Ansi.Color.RED, Ansi.Attribute.INTENSITY_BOLD) - protected final StyledOutput buildSuccessStyled = styled(buildSuccess, Ansi.Color.GREEN, Ansi.Attribute.INTENSITY_BOLD) - - abstract String getFailureMessage() - - abstract String getSuccessMessage() + protected final StyledOutput buildFailed = styled(Ansi.Color.RED, Ansi.Attribute.INTENSITY_BOLD).text('BUILD FAILED').off() + protected final StyledOutput buildSuccessful = styled(Ansi.Color.GREEN, Ansi.Attribute.INTENSITY_BOLD).text('BUILD SUCCESSFUL').off() def "outcome for successful build is logged with appropriate styling"() { given: @@ -50,9 +44,9 @@ abstract class AbstractConsoleBuildResultFunctionalTest extends AbstractConsoleG succeeds('all') then: - result.assertRawOutputContains(successMessage) - LogContent.of(result.output).removeAnsiChars().withNormalizedEol().matches """(?s).* -BUILD SUCCESSFUL in \\d+s\\n? + result.formattedOutput.contains(buildSuccessful.output) + result.plainTextOutput.matches """(?s).* +BUILD SUCCESSFUL in \\d+s 2 actionable tasks: 2 executed .*""" @@ -60,9 +54,9 @@ BUILD SUCCESSFUL in \\d+s\\n? succeeds('all') then: - result.assertRawOutputContains(successMessage) - LogContent.of(result.output).removeAnsiChars().withNormalizedEol().matches """(?s).* -BUILD SUCCESSFUL in \\d+s\\n? + result.formattedOutput.contains(buildSuccessful.output) + result.plainTextOutput.matches """(?s).* +BUILD SUCCESSFUL in \\d+s 2 actionable tasks: 1 executed, 1 up-to-date .*""" } @@ -95,9 +89,9 @@ BUILD SUCCESSFUL in \\d+s\\n? succeeds('success') then: - LogContent.of(result.output).removeAnsiChars().withNormalizedEol().matches """(?s).*build finished + result.plainTextOutput.matches """(?s).*build finished -BUILD SUCCESSFUL in \\d+s\\n? +BUILD SUCCESSFUL in \\d+s 1 actionable task: 1 executed .*""" } @@ -124,9 +118,9 @@ BUILD SUCCESSFUL in \\d+s\\n? // Check that the failure text appears either stdout or stderr def outputWithFailure = errorsShouldAppearOnStdout() ? failure.output : failure.error def outputWithoutFailure = errorsShouldAppearOnStdout() ? failure.error : failure.output - def outputWithFailureAndNoDebugging = LogContent.of(outputWithFailure).removeBlankLines().removeAnsiChars().removeDebugPrefix().withNormalizedEol() + def outputWithFailureAndNoDebugging = LogContent.of(outputWithFailure).ansiCharsToColorText().removeDebugPrefix().withNormalizedEol() - outputWithFailure.contains("Build failed with an exception.") + outputWithFailureAndNoDebugging.contains("FAILURE: Build failed with an exception.") outputWithFailureAndNoDebugging.contains(""" * What went wrong: Execution failed for task ':broken'. @@ -135,8 +129,7 @@ BUILD SUCCESSFUL in \\d+s\\n? !outputWithoutFailure.contains("Build failed with an exception.") !outputWithoutFailure.contains("* What went wrong:") - outputWithFailure.contains("BUILD FAILED") - failure.assertHasRawErrorOutput(failureMessage) + outputWithFailureAndNoDebugging.contains(buildFailed.errorOutput) where: level << [LogLevel.DEBUG, LogLevel.INFO, LogLevel.LIFECYCLE, LogLevel.WARN, LogLevel.QUIET] @@ -157,7 +150,7 @@ BUILD SUCCESSFUL in \\d+s\\n? fails("broken") and: - result.assertRawOutputContains("1 actionable task: 1 executed") + LogContent.of(result.output).ansiCharsToPlainText().withNormalizedEol().contains("1 actionable task: 1 executed") where: level << [LogLevel.DEBUG, LogLevel.INFO, LogLevel.LIFECYCLE] diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleVerboseBasicFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleVerboseBasicFunctionalTest.groovy index f3d34bd857912..eb83432210a4c 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleVerboseBasicFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleVerboseBasicFunctionalTest.groovy @@ -18,6 +18,7 @@ package org.gradle.internal.logging.console.taskgrouping import org.gradle.integtests.fixtures.console.AbstractConsoleGroupedTaskFunctionalTest +import static org.gradle.api.logging.configuration.ConsoleOutput.Auto import static org.gradle.api.logging.configuration.ConsoleOutput.Plain import static org.gradle.api.logging.configuration.ConsoleOutput.Verbose @@ -26,7 +27,7 @@ abstract class AbstractConsoleVerboseBasicFunctionalTest extends AbstractConsole given: def helloWorldMessage= 'Hello world' def byeWorldMessage= 'Bye world' - def hasSilenceTaskOutput = consoleType in [Verbose, Plain] + def hasSilenceTaskOutput = consoleType in [Verbose, Plain] || consoleType == Auto && !consoleAttachment.stdoutAttached buildFile << """ task helloWorld { @@ -55,4 +56,21 @@ abstract class AbstractConsoleVerboseBasicFunctionalTest extends AbstractConsole hasSilenceTaskOutput == result.groupedOutput.hasTask(':silence') } + def 'failed task result can be rendered'() { + given: + buildFile << ''' + task myFailure { + doLast { + assert false + } + } + ''' + + when: + fails('myFailure') + + then: + result.groupedOutput.task(':myFailure').outcome == 'FAILED' + } + } diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleVerboseRenderingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleVerboseRenderingFunctionalTest.groovy index ad65b8289d2ba..3df8d47ae775f 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleVerboseRenderingFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractConsoleVerboseRenderingFunctionalTest.groovy @@ -20,22 +20,6 @@ import org.gradle.integtests.fixtures.executer.GradleContextualExecuter import spock.lang.IgnoreIf abstract class AbstractConsoleVerboseRenderingFunctionalTest extends AbstractConsoleVerboseBasicFunctionalTest { - def 'failed task result can be rendered'() { - given: - buildFile << ''' - task myFailure { - doLast { - assert false - } - } - ''' - - when: - fails('myFailure') - - then: - result.groupedOutput.task(':myFailure').outcome == 'FAILED' - } def 'up-to-date task result can be rendered'() { given: diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractGroupedProjectConfigureLoggingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractGroupedProjectConfigureLoggingFunctionalTest.groovy index 150f212319d0b..b171e13c6be77 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractGroupedProjectConfigureLoggingFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/AbstractGroupedProjectConfigureLoggingFunctionalTest.groovy @@ -47,7 +47,7 @@ abstract class AbstractGroupedProjectConfigureLoggingFunctionalTest extends Abst run() then: - def normalizedOutput = LogContent.of(result.output).removeAnsiChars().withNormalizedEol() + def normalizedOutput = LogContent.of(result.output).ansiCharsToPlainText().withNormalizedEol() normalizedOutput.contains(""" > Configure project : root project diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/AutoConsoleBasicGroupedTaskLoggingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/AutoConsoleBasicGroupedTaskLoggingFunctionalTest.groovy new file mode 100644 index 0000000000000..e14a5f980a101 --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/AutoConsoleBasicGroupedTaskLoggingFunctionalTest.groovy @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.plain + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractBasicGroupedTaskLoggingFunctionalTest + +class AutoConsoleBasicGroupedTaskLoggingFunctionalTest extends AbstractBasicGroupedTaskLoggingFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/AutoConsoleCompositeBuildGroupedTaskFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/AutoConsoleCompositeBuildGroupedTaskFunctionalTest.groovy new file mode 100644 index 0000000000000..9633eef30e930 --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/AutoConsoleCompositeBuildGroupedTaskFunctionalTest.groovy @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.plain + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleCompositeBuildGroupedTaskFunctionalTest + +class AutoConsoleCompositeBuildGroupedTaskFunctionalTest extends AbstractConsoleCompositeBuildGroupedTaskFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/PlainConsoleBuildResultReportingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/PlainConsoleBuildResultReportingFunctionalTest.groovy index d310ff4fe85ee..760f8894b4f2f 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/PlainConsoleBuildResultReportingFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/plain/PlainConsoleBuildResultReportingFunctionalTest.groovy @@ -21,6 +21,4 @@ import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleBuildResu class PlainConsoleBuildResultReportingFunctionalTest extends AbstractConsoleBuildResultFunctionalTest { ConsoleOutput consoleType = ConsoleOutput.Plain - String failureMessage = buildFailed - String successMessage = buildSuccess } diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleDeprecationMessageGroupedTaskFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleDeprecationMessageGroupedTaskFunctionalTest.groovy new file mode 100644 index 0000000000000..ae2a131bd7ea9 --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleDeprecationMessageGroupedTaskFunctionalTest.groovy @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.rich + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleDeprecationMessageGroupedTaskFunctionalTest + +class AutoConsoleDeprecationMessageGroupedTaskFunctionalTest extends AbstractConsoleDeprecationMessageGroupedTaskFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleGradleBuildGroupedTaskFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleGradleBuildGroupedTaskFunctionalTest.groovy new file mode 100644 index 0000000000000..53a5e322cec46 --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleGradleBuildGroupedTaskFunctionalTest.groovy @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.rich + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleGradleBuildGroupedTaskFunctionalTest + +class AutoConsoleGradleBuildGroupedTaskFunctionalTest extends AbstractConsoleGradleBuildGroupedTaskFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleGroupedProjectConfigureLoggingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleGroupedProjectConfigureLoggingFunctionalTest.groovy new file mode 100644 index 0000000000000..e5e86bb772618 --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleGroupedProjectConfigureLoggingFunctionalTest.groovy @@ -0,0 +1,25 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.rich + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractGroupedProjectConfigureLoggingFunctionalTest + + +class AutoConsoleGroupedProjectConfigureLoggingFunctionalTest extends AbstractGroupedProjectConfigureLoggingFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleLoggingHooksFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleLoggingHooksFunctionalTest.groovy new file mode 100644 index 0000000000000..2bac0db5a6c91 --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleLoggingHooksFunctionalTest.groovy @@ -0,0 +1,25 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.rich + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractLoggingHooksFunctionalTest + + +class AutoConsoleLoggingHooksFunctionalTest extends AbstractLoggingHooksFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleVerboseBasicFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleVerboseBasicFunctionalTest.groovy new file mode 100644 index 0000000000000..dd9bbe520c2bf --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/AutoConsoleVerboseBasicFunctionalTest.groovy @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.rich + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleVerboseBasicFunctionalTest + +class AutoConsoleVerboseBasicFunctionalTest extends AbstractConsoleVerboseBasicFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/RichConsoleBasicGroupedTaskLoggingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/RichConsoleBasicGroupedTaskLoggingFunctionalTest.groovy index 6be503ede6ca8..77c87e89a88b1 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/RichConsoleBasicGroupedTaskLoggingFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/RichConsoleBasicGroupedTaskLoggingFunctionalTest.groovy @@ -18,7 +18,6 @@ package org.gradle.internal.logging.console.taskgrouping.rich import org.fusesource.jansi.Ansi import org.gradle.api.logging.configuration.ConsoleOutput -import org.gradle.integtests.fixtures.console.AbstractConsoleGroupedTaskFunctionalTest.StyledOutput import org.gradle.internal.logging.console.taskgrouping.AbstractBasicGroupedTaskLoggingFunctionalTest import spock.lang.Issue @@ -26,9 +25,9 @@ import spock.lang.Issue class RichConsoleBasicGroupedTaskLoggingFunctionalTest extends AbstractBasicGroupedTaskLoggingFunctionalTest { ConsoleOutput consoleType = ConsoleOutput.Rich - private final StyledOutput failingTask = styled("> Task :failing", Ansi.Color.RED, Ansi.Attribute.INTENSITY_BOLD) - private final StyledOutput succeedingTask = styled("> Task :succeeding", null, Ansi.Attribute.INTENSITY_BOLD) - private final StyledOutput configuringProject = styled("> Configure project :", Ansi.Color.RED, Ansi.Attribute.INTENSITY_BOLD) + private final StyledOutput failingTask = styled(Ansi.Color.RED, Ansi.Attribute.INTENSITY_BOLD).text("> Task :failing").styled(null).text(" FAILED").off() + private final StyledOutput succeedingTask = styled(Ansi.Attribute.INTENSITY_BOLD).text("> Task :succeeding").off() + private final StyledOutput configuringProject = styled(Ansi.Color.RED, Ansi.Attribute.INTENSITY_BOLD).text("> Configure project :").off() @Issue("gradle/gradle#2038") def "tasks with no actions are not displayed"() { @@ -56,7 +55,7 @@ class RichConsoleBasicGroupedTaskLoggingFunctionalTest extends AbstractBasicGrou then: result.groupedOutput.task(':failing').output == 'hello' - result.assertRawOutputContains(failingTask.output) + result.formattedOutput.contains(failingTask.output) } def "group header is printed red if task failed and there is no output"() { @@ -71,7 +70,7 @@ class RichConsoleBasicGroupedTaskLoggingFunctionalTest extends AbstractBasicGrou fails('failing') then: - result.assertRawOutputContains(failingTask.output) + result.formattedOutput.contains(failingTask.output) } def "group header is printed white if task succeeds"() { @@ -86,7 +85,7 @@ class RichConsoleBasicGroupedTaskLoggingFunctionalTest extends AbstractBasicGrou succeeds('succeeding') then: - result.assertRawOutputContains(succeedingTask.output) + result.formattedOutput.contains(succeedingTask.output) } def "configure project group header is printed red if configuration fails with additional failures"() { @@ -104,7 +103,7 @@ class RichConsoleBasicGroupedTaskLoggingFunctionalTest extends AbstractBasicGrou fails('failing') then: - result.assertRawOutputContains(configuringProject.output) + result.formattedOutput.contains(configuringProject.output) } def "tasks that complete without output do not break up other task output"() { diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/RichConsoleBuildResultReportingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/RichConsoleBuildResultReportingFunctionalTest.groovy index 0c04297b0b39c..afe1a09b49d40 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/RichConsoleBuildResultReportingFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/rich/RichConsoleBuildResultReportingFunctionalTest.groovy @@ -22,6 +22,4 @@ import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleBuildResu class RichConsoleBuildResultReportingFunctionalTest extends AbstractConsoleBuildResultFunctionalTest { ConsoleOutput consoleType = ConsoleOutput.Rich - String failureMessage = buildFailedStyled.errorOutput - String successMessage = buildSuccessStyled.output } diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/AutoConsoleBuildResultReportingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/AutoConsoleBuildResultReportingFunctionalTest.groovy new file mode 100644 index 0000000000000..1f58c1e6541b3 --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/AutoConsoleBuildResultReportingFunctionalTest.groovy @@ -0,0 +1,25 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.verbose + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleBuildResultFunctionalTest + + +class AutoConsoleBuildResultReportingFunctionalTest extends AbstractConsoleBuildResultFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/AutoConsoleBuildSrcGroupedTaskFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/AutoConsoleBuildSrcGroupedTaskFunctionalTest.groovy new file mode 100644 index 0000000000000..00e8c9309b9be --- /dev/null +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/AutoConsoleBuildSrcGroupedTaskFunctionalTest.groovy @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.console.taskgrouping.verbose + +import org.gradle.api.logging.configuration.ConsoleOutput +import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleBuildSrcGroupedTaskFunctionalTest + +class AutoConsoleBuildSrcGroupedTaskFunctionalTest extends AbstractConsoleBuildSrcGroupedTaskFunctionalTest { + ConsoleOutput consoleType = ConsoleOutput.Auto +} diff --git a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/VerboseConsoleBuildResultReportingFunctionalTest.groovy b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/VerboseConsoleBuildResultReportingFunctionalTest.groovy index c7f7a09f2cd8a..085b9a53019d2 100644 --- a/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/VerboseConsoleBuildResultReportingFunctionalTest.groovy +++ b/subprojects/logging/src/integTest/groovy/org/gradle/internal/logging/console/taskgrouping/verbose/VerboseConsoleBuildResultReportingFunctionalTest.groovy @@ -22,6 +22,4 @@ import org.gradle.internal.logging.console.taskgrouping.AbstractConsoleBuildResu class VerboseConsoleBuildResultReportingFunctionalTest extends AbstractConsoleBuildResultFunctionalTest { ConsoleOutput consoleType = ConsoleOutput.Verbose - String failureMessage = buildFailedStyled.errorOutput - String successMessage = buildSuccessStyled.output } diff --git a/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/ConsoleConfigureAction.java b/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/ConsoleConfigureAction.java index 766a6f2119fdb..e18340c046bc3 100644 --- a/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/ConsoleConfigureAction.java +++ b/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/ConsoleConfigureAction.java @@ -18,6 +18,7 @@ import org.gradle.api.logging.configuration.ConsoleOutput; import org.gradle.internal.logging.console.AnsiConsole; +import org.gradle.internal.logging.console.ColorMap; import org.gradle.internal.logging.console.Console; import org.gradle.internal.nativeintegration.console.ConsoleDetector; import org.gradle.internal.nativeintegration.console.ConsoleMetaData; @@ -25,78 +26,85 @@ import org.gradle.internal.nativeintegration.console.TestConsoleMetadata; import org.gradle.internal.nativeintegration.services.NativeServices; -import javax.annotation.Nullable; import java.io.OutputStream; import java.io.OutputStreamWriter; public class ConsoleConfigureAction { public static void execute(OutputEventRenderer renderer, ConsoleOutput consoleOutput) { + execute(renderer, consoleOutput, getConsoleMetaData(), renderer.getOriginalStdOut(), renderer.getOriginalStdErr()); + } + + public static void execute(OutputEventRenderer renderer, ConsoleOutput consoleOutput, ConsoleMetaData consoleMetadata, OutputStream stdout, OutputStream stderr) { if (consoleOutput == ConsoleOutput.Auto) { - configureAutoConsole(renderer); + configureAutoConsole(renderer, consoleMetadata, stdout, stderr); } else if (consoleOutput == ConsoleOutput.Rich) { - configureRichConsole(renderer, false); + configureRichConsole(renderer, consoleMetadata, stdout, stderr, false); } else if (consoleOutput == ConsoleOutput.Verbose) { - configureRichConsole(renderer, true); + configureRichConsole(renderer, consoleMetadata, stdout, stderr, true); } else if (consoleOutput == ConsoleOutput.Plain) { - configurePlainConsole(renderer); + configurePlainConsole(renderer, consoleMetadata, stdout, stderr); } } - private static void configureRichConsole(OutputEventRenderer renderer, boolean verbose) { - ConsoleMetaData consoleMetaData = getConsoleMetaData(); - configureRichConsole(renderer, consoleMetaData, shouldForce(consoleMetaData), verbose); - } - - private static boolean shouldForce(ConsoleMetaData consoleMetaData) { - return consoleMetaData == null || consoleMetaData instanceof TestConsoleMetadata; - } - - private static void configureAutoConsole(OutputEventRenderer renderer) { - ConsoleMetaData consoleMetaData = getConsoleMetaData(); - if (consoleMetaData != null) { - configureRichConsole(renderer, consoleMetaData, false, false); - } else { - configurePlainConsole(renderer, null); - } - } - - private static void configurePlainConsole(OutputEventRenderer renderer) { - ConsoleMetaData consoleMetaData = getConsoleMetaData(); - configurePlainConsole(renderer, consoleMetaData); - } - - @Nullable private static ConsoleMetaData getConsoleMetaData() { String testConsole = System.getProperty(TestConsoleMetadata.TEST_CONSOLE_PROPERTY); if (testConsole != null) { return TestConsoleMetadata.valueOf(testConsole); } ConsoleDetector consoleDetector = NativeServices.getInstance().get(ConsoleDetector.class); - return consoleDetector.getConsole(); + ConsoleMetaData metaData = consoleDetector.getConsole(); + if (metaData != null) { + return metaData; + } + return FallbackConsoleMetaData.NOT_ATTACHED; } - private static void configurePlainConsole(OutputEventRenderer renderer, ConsoleMetaData consoleMetaData) { - // Redirect stderr to stdout if a console is attached to both stdout and stderr - renderer.addPlainConsole(consoleMetaData != null && consoleMetaData.isStdOut() && consoleMetaData.isStdErr()); + private static void configureAutoConsole(OutputEventRenderer renderer, ConsoleMetaData consoleMetaData, OutputStream stdout, OutputStream stderr) { + if (consoleMetaData.isStdOut() && consoleMetaData.isStdErr()) { + // Redirect stderr to stdout when both stdout and stderr are attached to a console. Assume that they are attached to the same console + // This avoids interleaving problems when stdout and stderr end up at the same location + Console console = consoleFor(stdout, consoleMetaData, renderer.getColourMap()); + renderer.addRichConsoleWithErrorOutputOnStdout(console, consoleMetaData, false); + } else if (consoleMetaData.isStdOut()) { + // Write rich content to stdout and plain content to stderr + Console console = consoleFor(stdout, consoleMetaData, renderer.getColourMap()); + renderer.addRichConsole(console, stderr, consoleMetaData, false); + } else if (consoleMetaData.isStdErr()) { + // Write plain content to stdout and rich content to stderr + Console stderrConsole = consoleFor(stderr, consoleMetaData, renderer.getColourMap()); + renderer.addRichConsole(stdout, stderrConsole, true); + } else { + renderer.addPlainConsole(stdout, stderr); + } } - private static void configureRichConsole(OutputEventRenderer renderer, @Nullable ConsoleMetaData consoleMetaData, boolean force, boolean verbose) { - if (consoleMetaData == null) { - consoleMetaData = FallbackConsoleMetaData.ATTACHED; + private static void configurePlainConsole(OutputEventRenderer renderer, ConsoleMetaData consoleMetaData, OutputStream stdout, OutputStream stderr) { + if (consoleMetaData.isStdOut() && consoleMetaData.isStdErr()) { + // Redirect stderr to stdout when both stdout and stderr are attached to a console. Assume that they are attached to the same console + // This avoids interleaving problems when stdout and stderr end up at the same location + renderer.addPlainConsoleWithErrorOutputOnStdout(stdout); + } else { + renderer.addPlainConsole(stdout, stderr); } - if (consoleMetaData.isStdOut()) { - OutputStream originalStdOut = renderer.getOriginalStdOut(); - OutputStreamWriter outStr = new OutputStreamWriter(force ? originalStdOut : AnsiConsoleUtil.wrapOutputStream(originalStdOut)); - Console console = new AnsiConsole(outStr, outStr, renderer.getColourMap(), consoleMetaData, force); - renderer.addRichConsole(console, consoleMetaData, verbose); - } else if (consoleMetaData.isStdErr()) { - // Only stderr is connected to a terminal - OutputStream originalStdErr = renderer.getOriginalStdErr(); - OutputStreamWriter errStr = new OutputStreamWriter(force ? originalStdErr : AnsiConsoleUtil.wrapOutputStream(originalStdErr)); - Console console = new AnsiConsole(errStr, errStr, renderer.getColourMap(), consoleMetaData, force); - renderer.addRichConsole(console, consoleMetaData, verbose); + } + + private static void configureRichConsole(OutputEventRenderer renderer, ConsoleMetaData consoleMetaData, OutputStream stdout, OutputStream stderr, boolean verbose) { + if (consoleMetaData.isStdOut() && consoleMetaData.isStdErr()) { + // Redirect stderr to stdout when both stdout and stderr are attached to a console. Assume that they are attached to the same console + // This avoids interleaving problems when stdout and stderr end up at the same location + Console console = consoleFor(stdout, consoleMetaData, renderer.getColourMap()); + renderer.addRichConsoleWithErrorOutputOnStdout(console, consoleMetaData, verbose); } else { - renderer.addRichConsole(null, consoleMetaData, verbose); + // Write rich content to both stdout and stderr + Console stdoutConsole = consoleFor(stdout, consoleMetaData, renderer.getColourMap()); + Console stderrConsole = consoleFor(stderr, consoleMetaData, renderer.getColourMap()); + renderer.addRichConsole(stdoutConsole, stderrConsole, consoleMetaData, verbose); } } + + private static Console consoleFor(OutputStream stdout, ConsoleMetaData consoleMetaData, ColorMap colourMap) { + boolean force = !consoleMetaData.isWrapStreams(); + OutputStreamWriter outStr = new OutputStreamWriter(force ? stdout : AnsiConsoleUtil.wrapOutputStream(stdout)); + return new AnsiConsole(outStr, outStr, colourMap, consoleMetaData, force); + } } diff --git a/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/ErrorOutputDispatchingListener.java b/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/ErrorOutputDispatchingListener.java index f6e43e428723e..0f183c1117a25 100644 --- a/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/ErrorOutputDispatchingListener.java +++ b/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/ErrorOutputDispatchingListener.java @@ -23,12 +23,10 @@ class ErrorOutputDispatchingListener implements OutputEventListener { private final OutputEventListener stderrChain; private final OutputEventListener stdoutChain; - private final boolean redirectStderr; - public ErrorOutputDispatchingListener(OutputEventListener stderrChain, OutputEventListener stdoutChain, boolean redirectStderr) { + public ErrorOutputDispatchingListener(OutputEventListener stderrChain, OutputEventListener stdoutChain) { this.stderrChain = stderrChain; this.stdoutChain = stdoutChain; - this.redirectStderr = redirectStderr; } @Override @@ -39,14 +37,8 @@ public void onOutput(OutputEvent event) { } else if (event.getLogLevel() != LogLevel.ERROR) { stdoutChain.onOutput(event); } else { - // Write anything that isn't the build failure report to the output stream if there is a console attached. - // This is to avoid interleaving of error and non-error output generated at approximately the same time, as the process stdout and stderr may be forwarded on different pipes and so ordering is lost - // TODO - should attempt to flush the output stream prior to writing to the error stream - if (redirectStderr) { - stdoutChain.onOutput(event); - } else { - stderrChain.onOutput(event); - } + // TODO - should attempt to flush the output stream prior to writing to the error stream (and vice versa) + stderrChain.onOutput(event); } } } diff --git a/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/OutputEventRenderer.java b/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/OutputEventRenderer.java index 4be5ecf128ac3..654b0241f7f56 100644 --- a/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/OutputEventRenderer.java +++ b/subprojects/logging/src/main/java/org/gradle/internal/logging/sink/OutputEventRenderer.java @@ -16,14 +16,13 @@ package org.gradle.internal.logging.sink; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.logging.LogLevel; import org.gradle.api.logging.StandardOutputListener; import org.gradle.api.logging.configuration.ConsoleOutput; import org.gradle.internal.Factory; import org.gradle.internal.event.ListenerBroadcast; import org.gradle.internal.logging.config.LoggingRouter; -import org.gradle.internal.logging.console.AnsiConsole; import org.gradle.internal.logging.console.BuildLogLevelFilterRenderer; import org.gradle.internal.logging.console.BuildStatusRenderer; import org.gradle.internal.logging.console.ColorMap; @@ -55,7 +54,6 @@ import javax.annotation.Nullable; import java.io.OutputStream; -import java.io.OutputStreamWriter; import java.util.concurrent.atomic.AtomicReference; /** @@ -159,25 +157,9 @@ public void attachConsole(OutputStream outputStream, OutputStream errorStream, C public void attachConsole(OutputStream outputStream, OutputStream errorStream, ConsoleOutput consoleOutput, @Nullable ConsoleMetaData consoleMetadata) { synchronized (lock) { if (consoleMetadata == null) { - consoleMetadata = consoleOutput == ConsoleOutput.Plain ? FallbackConsoleMetaData.NOT_ATTACHED : FallbackConsoleMetaData.ATTACHED; - } - StandardOutputListener outputListener = new StreamBackedStandardOutputListener(outputStream); - StandardOutputListener errorListener = new StreamBackedStandardOutputListener(errorStream); - if (consoleOutput == ConsoleOutput.Plain) { - addPlainConsole(outputListener, errorListener, consoleMetadata.isStdOut() && consoleMetadata.isStdErr()); - } else { - Console console; - if (consoleMetadata.isStdOut()) { - OutputStreamWriter writer = new OutputStreamWriter(outputStream); - console = new AnsiConsole(writer, writer, getColourMap(), consoleMetadata, true); - } else if (consoleMetadata.isStdErr()) { - OutputStreamWriter writer = new OutputStreamWriter(errorStream); - console = new AnsiConsole(writer, writer, getColourMap(), consoleMetadata, true); - } else { - console = null; - } - addRichConsole(console, outputListener, errorListener, consoleMetadata, consoleOutput == ConsoleOutput.Verbose); + consoleMetadata = FallbackConsoleMetaData.NOT_ATTACHED; } + ConsoleConfigureAction.execute(this, consoleOutput, consoleMetadata, outputStream, errorStream); } } @@ -248,37 +230,49 @@ public void removeOutputEventListener(OutputEventListener listener) { } } - public OutputEventRenderer addRichConsole(Console console, ConsoleMetaData consoleMetaData) { - return addRichConsole(console, consoleMetaData, false); + public void addRichConsoleWithErrorOutputOnStdout(Console stdout, ConsoleMetaData consoleMetaData, boolean verbose) { + OutputEventListener consoleListener = new StyledTextOutputBackedRenderer(stdout.getBuildOutputArea()); + OutputEventListener consoleChain = getConsoleChainWithDynamicStdout(stdout, consoleMetaData, verbose, consoleListener); + addConsoleChain(consoleChain); } - public OutputEventRenderer addRichConsole(Console console, ConsoleMetaData consoleMetaData, boolean verbose) { - return addRichConsole(console, new StreamBackedStandardOutputListener((Appendable) originalStdOut), new StreamBackedStandardOutputListener((Appendable) originalStdErr), consoleMetaData, verbose); + public void addRichConsole(Console stdout, Console stderr, ConsoleMetaData consoleMetaData, boolean verbose) { + OutputEventListener stdoutChain = new StyledTextOutputBackedRenderer(stdout.getBuildOutputArea()); + OutputEventListener stderrChain = new FlushConsoleListener(stderr, new StyledTextOutputBackedRenderer(stderr.getBuildOutputArea())); + OutputEventListener consoleListener = new ErrorOutputDispatchingListener(stderrChain, stdoutChain); + OutputEventListener consoleChain = getConsoleChainWithDynamicStdout(stdout, consoleMetaData, verbose, consoleListener); + addConsoleChain(consoleChain); } - private OutputEventRenderer addRichConsole(Console console, StandardOutputListener outputListener, StandardOutputListener errorListener, ConsoleMetaData consoleMetaData, boolean verbose) { - OutputEventListener consoleChain; - boolean stdoutAttachedToConsole = consoleMetaData.isStdOut(); - boolean stderrAttachedToConsole = consoleMetaData.isStdErr(); - if (stdoutAttachedToConsole && stderrAttachedToConsole) { - OutputEventListener consoleListener = new StyledTextOutputBackedRenderer(console.getBuildOutputArea()); - consoleChain = getRichConsoleChain(console, consoleMetaData, verbose, consoleListener); - } else if (stdoutAttachedToConsole) { - OutputEventListener stderrChain = new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(errorListener)); - OutputEventListener consoleListener = new ErrorOutputDispatchingListener(stderrChain, new StyledTextOutputBackedRenderer(console.getBuildOutputArea()), false); - consoleChain = getRichConsoleChain(console, consoleMetaData, verbose, consoleListener); - } else if (stderrAttachedToConsole) { - OutputEventListener stdoutChain = new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(outputListener)); - OutputEventListener stderrChain = new FlushConsoleListener(console, new StyledTextOutputBackedRenderer(console.getBuildOutputArea())); - consoleChain = getPlainConsoleChain(stdoutChain, stderrChain, false, verbose); - } else { - consoleChain = getPlainConsoleChain(outputListener, errorListener, false, verbose); - } + public void addRichConsole(Console stdout, OutputStream stderr, ConsoleMetaData consoleMetaData, boolean verbose) { + OutputEventListener stdoutChain = new StyledTextOutputBackedRenderer(stdout.getBuildOutputArea()); + OutputEventListener stderrChain = new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(new StreamBackedStandardOutputListener(stderr))); + OutputEventListener consoleListener = new ErrorOutputDispatchingListener(stderrChain, stdoutChain); + OutputEventListener consoleChain = getConsoleChainWithDynamicStdout(stdout, consoleMetaData, verbose, consoleListener); + addConsoleChain(consoleChain); + } + + public void addRichConsole(OutputStream stdout, Console stderr, boolean verbose) { + OutputEventListener stdoutChain = new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(new StreamBackedStandardOutputListener(stdout))); + OutputEventListener stderrChain = new FlushConsoleListener(stderr, new StyledTextOutputBackedRenderer(stderr.getBuildOutputArea())); + OutputEventListener consoleListener = new ErrorOutputDispatchingListener(stderrChain, stdoutChain); + OutputEventListener consoleChain = getConsoleChainWithoutDynamicStdout(consoleListener, verbose); + addConsoleChain(consoleChain); + } - return addConsoleChain(consoleChain); + public void addPlainConsoleWithErrorOutputOnStdout(OutputStream stdout) { + OutputEventListener stdoutChain = new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(new StreamBackedStandardOutputListener(stdout))); + addConsoleChain(getConsoleChainWithoutDynamicStdout(stdoutChain, true)); } - private OutputEventListener getRichConsoleChain(Console console, ConsoleMetaData consoleMetaData, boolean verbose, OutputEventListener consoleListener) { + public void addPlainConsole(OutputStream stdout, OutputStream stderr) { + OutputEventListener stdoutChain = new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(new StreamBackedStandardOutputListener(stdout))); + OutputEventListener stderrChain = new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(new StreamBackedStandardOutputListener(stderr))); + OutputEventListener outputListener = new ErrorOutputDispatchingListener(stderrChain, stdoutChain); + addConsoleChain(getConsoleChainWithoutDynamicStdout(outputListener, true)); + } + + private OutputEventListener getConsoleChainWithDynamicStdout(Console console, ConsoleMetaData consoleMetaData, boolean verbose, OutputEventListener consoleListener) { return throttled( new UserInputConsoleRenderer( new BuildStatusRenderer( @@ -290,35 +284,22 @@ private OutputEventListener getRichConsoleChain(Console console, ConsoleMetaData new DefaultWorkInProgressFormatter(consoleMetaData), new ConsoleLayoutCalculator(consoleMetaData) ), - console.getStatusBar(), console, consoleMetaData), - console) + console.getStatusBar(), console, consoleMetaData), + console) ); } - public OutputEventRenderer addPlainConsole(boolean redirectStderr) { - return addPlainConsole(new StreamBackedStandardOutputListener((Appendable) originalStdOut), new StreamBackedStandardOutputListener((Appendable) originalStdErr), redirectStderr); - } - - private OutputEventRenderer addPlainConsole(StandardOutputListener outputListener, StandardOutputListener errorListener, boolean redirectStderr) { - return addConsoleChain(getPlainConsoleChain(outputListener, errorListener, redirectStderr, true)); - } - - private OutputEventListener getPlainConsoleChain(StandardOutputListener outputListener, StandardOutputListener errorListener, boolean redirectStderr, boolean verbose) { - final OutputEventListener stdoutChain = new UserInputStandardOutputRenderer(new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(outputListener))); - final OutputEventListener stderrChain = new StyledTextOutputBackedRenderer(new StreamingStyledTextOutput(errorListener)); - return getPlainConsoleChain(stdoutChain, stderrChain, redirectStderr, verbose); - } - - private OutputEventListener getPlainConsoleChain(OutputEventListener stdoutChain, OutputEventListener stderrChain, boolean redirectStderr, boolean verbose) { + private OutputEventListener getConsoleChainWithoutDynamicStdout(OutputEventListener outputListener, boolean verbose) { return throttled( - new BuildLogLevelFilterRenderer( - new GroupingProgressLogEventGenerator( - new ErrorOutputDispatchingListener(stderrChain, stdoutChain, redirectStderr), - new PrettyPrefixedLogHeaderFormatter(), - verbose + new UserInputStandardOutputRenderer( + new BuildLogLevelFilterRenderer( + new GroupingProgressLogEventGenerator( + outputListener, + new PrettyPrefixedLogHeaderFormatter(), + verbose + ) ) - ) - ); + )); } private OutputEventListener throttled(OutputEventListener consoleChain) { diff --git a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyValueVisitor.java b/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/BuildOperationAwareLogger.java similarity index 58% rename from subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyValueVisitor.java rename to subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/BuildOperationAwareLogger.java index 6d13abb09dce9..8b009131902a9 100644 --- a/subprojects/core/src/main/java/org/gradle/api/internal/tasks/properties/PropertyValueVisitor.java +++ b/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/BuildOperationAwareLogger.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,14 @@ * limitations under the License. */ -package org.gradle.api.internal.tasks.properties; +package org.gradle.internal.logging.slf4j; -import org.gradle.internal.reflect.PropertyMetadata; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.Logger; +import org.gradle.internal.operations.OperationIdentifier; -public interface PropertyValueVisitor { - boolean shouldVisit(PropertyVisitor visitor); +abstract class BuildOperationAwareLogger implements Logger { + + abstract void log(LogLevel logLevel, Throwable throwable, String message, OperationIdentifier operationIdentifier); - void visitPropertyValue(String propertyName, PropertyValue value, PropertyMetadata propertyMetadata, PropertyVisitor visitor, BeanPropertyContext context); } diff --git a/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/ContextAwareTaskLogger.java b/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/ContextAwareTaskLogger.java new file mode 100644 index 0000000000000..e210f9cf229d3 --- /dev/null +++ b/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/ContextAwareTaskLogger.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.slf4j; + +import org.gradle.api.logging.Logger; +import org.gradle.internal.operations.OperationIdentifier; + +public interface ContextAwareTaskLogger extends Logger { + void setFallbackBuildOperationId(OperationIdentifier operationIdentifier); +} diff --git a/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/DefaultContextAwareTaskLogger.java b/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/DefaultContextAwareTaskLogger.java new file mode 100644 index 0000000000000..d5c540fe15341 --- /dev/null +++ b/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/DefaultContextAwareTaskLogger.java @@ -0,0 +1,482 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.logging.slf4j; + +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.Logger; +import org.gradle.api.logging.Logging; +import org.gradle.internal.Cast; +import org.gradle.internal.operations.CurrentBuildOperationRef; +import org.gradle.internal.operations.OperationIdentifier; +import org.slf4j.Marker; +import org.slf4j.helpers.FormattingTuple; +import org.slf4j.helpers.MessageFormatter; + +public class DefaultContextAwareTaskLogger implements ContextAwareTaskLogger { + + private BuildOperationAwareLogger delegate; + private OperationIdentifier fallbackOperationIdentifier = null; + + public DefaultContextAwareTaskLogger(Logger delegate) { + this.delegate = Cast.cast(BuildOperationAwareLogger.class, delegate); + } + + public void setFallbackBuildOperationId(OperationIdentifier operationIdentifier) { + this.fallbackOperationIdentifier = operationIdentifier; + } + + @Override + public boolean isLifecycleEnabled() { + return delegate.isLifecycleEnabled(); + } + + @Override + public boolean isQuietEnabled() { + return delegate.isQuietEnabled(); + } + + @Override + public boolean isEnabled(LogLevel level) { + return delegate.isEnabled(level); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public boolean isTraceEnabled() { + return delegate.isTraceEnabled(); + } + + @Override + public boolean isTraceEnabled(Marker marker) { + return delegate.isTraceEnabled(marker); + } + + @Override + public boolean isDebugEnabled() { + return delegate.isDebugEnabled(); + } + + @Override + public boolean isDebugEnabled(Marker marker) { + return delegate.isDebugEnabled(marker); + } + + @Override + public boolean isInfoEnabled() { + return delegate.isInfoEnabled(); + } + + @Override + public boolean isInfoEnabled(Marker marker) { + return delegate.isInfoEnabled(marker); + } + + @Override + public boolean isWarnEnabled() { + return delegate.isWarnEnabled(); + } + + @Override + public boolean isWarnEnabled(Marker marker) { + return delegate.isWarnEnabled(marker); + } + + @Override + public boolean isErrorEnabled() { + return delegate.isErrorEnabled(); + } + + @Override + public boolean isErrorEnabled(Marker marker) { + return delegate.isErrorEnabled(marker); + } + + public void trace(String msg) { + } + + public void trace(String format, Object arg) { + } + + public void trace(String format, Object arg1, Object arg2) { + } + + public void trace(String format, Object... arguments) { + } + + public void trace(String msg, Throwable t) { + } + + public void trace(Marker marker, String msg) { + } + + public void trace(Marker marker, String format, Object arg) { + } + + public void trace(Marker marker, String format, Object arg1, Object arg2) { + } + + public void trace(Marker marker, String format, Object... argArray) { + } + + public void trace(Marker marker, String msg, Throwable t) { + } + + private void log(LogLevel logLevel, Throwable throwable, String message) { + OperationIdentifier buildOperationId = CurrentBuildOperationRef.instance().getId(); + if (buildOperationId == null) { + buildOperationId = fallbackOperationIdentifier; + } + delegate.log(logLevel, throwable, message, buildOperationId); + } + + private void log(LogLevel logLevel, Throwable throwable, String format, Object arg) { + log(logLevel, throwable, format, new Object[]{arg}); + } + + private void log(LogLevel logLevel, Throwable throwable, String format, Object arg1, Object arg2) { + log(logLevel, throwable, format, new Object[]{arg1, arg2}); + } + + private void log(LogLevel logLevel, Throwable throwable, String format, Object[] args) { + FormattingTuple tuple = MessageFormatter.arrayFormat(format, args); + Throwable loggedThrowable = throwable == null ? tuple.getThrowable() : throwable; + + log(logLevel, loggedThrowable, tuple.getMessage()); + } + + public void debug(String message) { + if (isDebugEnabled()) { + log(LogLevel.DEBUG, null, message); + } + } + + public void debug(String format, Object arg) { + if (isDebugEnabled()) { + log(LogLevel.DEBUG, null, format, arg); + } + } + + public void debug(String format, Object arg1, Object arg2) { + if (isDebugEnabled()) { + log(LogLevel.DEBUG, null, format, arg1, arg2); + } + } + + public void debug(String format, Object... arguments) { + if (isDebugEnabled()) { + log(LogLevel.DEBUG, null, format, arguments); + } + } + + public void debug(String msg, Throwable t) { + if (isDebugEnabled()) { + log(LogLevel.DEBUG, t, msg); + } + } + + public void debug(Marker marker, String msg) { + if (isDebugEnabled(marker)) { + log(LogLevel.DEBUG, null, msg); + } + } + + public void debug(Marker marker, String format, Object arg) { + if (isDebugEnabled(marker)) { + log(LogLevel.DEBUG, null, format, arg); + } + } + + public void debug(Marker marker, String format, Object arg1, Object arg2) { + if (isDebugEnabled(marker)) { + log(LogLevel.DEBUG, null, format, arg1, arg2); + } + } + + public void debug(Marker marker, String format, Object... argArray) { + if (isDebugEnabled(marker)) { + log(LogLevel.DEBUG, null, format, argArray); + } + } + + public void debug(Marker marker, String msg, Throwable t) { + if (isDebugEnabled(marker)) { + log(LogLevel.DEBUG, t, msg); + } + } + + public void info(String message) { + if (isInfoEnabled()) { + log(LogLevel.INFO, null, message); + } + } + + public void info(String format, Object arg) { + if (isInfoEnabled()) { + log(LogLevel.INFO, null, format, arg); + } + } + + public void info(String format, Object arg1, Object arg2) { + if (isInfoEnabled()) { + log(LogLevel.INFO, null, format, arg1, arg2); + } + } + + public void info(String format, Object... arguments) { + if (isInfoEnabled()) { + log(LogLevel.INFO, null, format, arguments); + } + } + + @Override + public void lifecycle(String message) { + if (isLifecycleEnabled()) { + log(LogLevel.LIFECYCLE, null, message); + } + } + + @Override + public void lifecycle(String message, Object... objects) { + if (isLifecycleEnabled()) { + log(LogLevel.LIFECYCLE, null, message, objects); + } + } + + @Override + public void lifecycle(String message, Throwable throwable) { + if (isLifecycleEnabled()) { + log(LogLevel.LIFECYCLE, throwable, message); + } + } + + + @Override + public void quiet(String message) { + if (isQuietEnabled()) { + log(LogLevel.QUIET, null, message); + } + } + + @Override + public void quiet(String message, Object... objects) { + if (isQuietEnabled()) { + log(LogLevel.QUIET, null, message, objects); + } + } + + @Override + public void quiet(String message, Throwable throwable) { + if (isQuietEnabled()) { + log(LogLevel.QUIET, throwable, message); + } + } + + @Override + public void log(LogLevel level, String message) { + if (isEnabled(level)) { + log(level, null, message); + } + } + + @Override + public void log(LogLevel level, String message, Object... objects) { + if (isEnabled(level)) { + log(level, null, message, objects); + } + } + + @Override + public void log(LogLevel level, String message, Throwable throwable) { + if (isEnabled(level)) { + log(level, throwable, message); + } + } + + public void info(String msg, Throwable t) { + if (isInfoEnabled()) { + log(LogLevel.INFO, t, msg); + } + } + + private LogLevel toLogLevel(Marker marker) { + if (marker == null) { + return LogLevel.INFO; + } + if (marker == Logging.LIFECYCLE) { + return LogLevel.LIFECYCLE; + } + if (marker == Logging.QUIET) { + return LogLevel.QUIET; + } + return LogLevel.INFO; + } + + public void info(Marker marker, String msg) { + if (isInfoEnabled(marker)) { + log(toLogLevel(marker), null, msg); + } + } + + public void info(Marker marker, String format, Object arg) { + if (isInfoEnabled(marker)) { + log(toLogLevel(marker), null, format, arg); + } + } + + public void info(Marker marker, String format, Object arg1, Object arg2) { + if (isInfoEnabled(marker)) { + log(toLogLevel(marker), null, format, arg1, arg2); + } + } + + public void info(Marker marker, String format, Object... argArray) { + if (isInfoEnabled(marker)) { + log(toLogLevel(marker), null, format, argArray); + } + } + + public void info(Marker marker, String msg, Throwable t) { + if (isInfoEnabled(marker)) { + log(toLogLevel(marker), t, msg); + } + } + + public void warn(String message) { + if (isWarnEnabled()) { + log(LogLevel.WARN, null, message); + } + } + + public void warn(String format, Object arg) { + if (isWarnEnabled()) { + log(LogLevel.WARN, null, format, arg); + } + } + + public void warn(String format, Object arg1, Object arg2) { + if (isWarnEnabled()) { + log(LogLevel.WARN, null, format, arg1, arg2); + } + } + + public void warn(String format, Object... arguments) { + if (isWarnEnabled()) { + log(LogLevel.WARN, null, format, arguments); + } + } + + public void warn(String msg, Throwable t) { + if (isWarnEnabled()) { + log(LogLevel.WARN, t, msg); + } + } + + public void warn(Marker marker, String msg) { + if (isWarnEnabled(marker)) { + log(LogLevel.WARN, null, msg); + } + } + + public void warn(Marker marker, String format, Object arg) { + if (isWarnEnabled(marker)) { + log(LogLevel.WARN, null, format, arg); + } + } + + public void warn(Marker marker, String format, Object arg1, Object arg2) { + if (isWarnEnabled(marker)) { + log(LogLevel.WARN, null, format, arg1, arg2); + } + } + + public void warn(Marker marker, String format, Object... argArray) { + if (isWarnEnabled(marker)) { + log(LogLevel.WARN, null, format, argArray); + } + } + + public void warn(Marker marker, String msg, Throwable t) { + if (isWarnEnabled(marker)) { + log(LogLevel.WARN, t, msg); + } + } + + public void error(String message) { + if (isErrorEnabled()) { + log(LogLevel.ERROR, null, message); + } + } + + public void error(String format, Object arg) { + if (isErrorEnabled()) { + log(LogLevel.ERROR, null, format, arg); + } + } + + public void error(String format, Object arg1, Object arg2) { + if (isErrorEnabled()) { + log(LogLevel.ERROR, null, format, arg1, arg2); + } + } + + public void error(String format, Object... arguments) { + if (isErrorEnabled()) { + log(LogLevel.ERROR, null, format, arguments); + } + } + + public void error(String msg, Throwable t) { + if (isErrorEnabled()) { + log(LogLevel.ERROR, t, msg); + } + } + + public void error(Marker marker, String msg) { + if (isErrorEnabled(marker)) { + log(LogLevel.ERROR, null, msg); + } + } + + public void error(Marker marker, String format, Object arg) { + if (isErrorEnabled(marker)) { + log(LogLevel.ERROR, null, format, arg); + } + } + + public void error(Marker marker, String format, Object arg1, Object arg2) { + if (isErrorEnabled(marker)) { + log(LogLevel.ERROR, null, format, arg1, arg2); + } + } + + public void error(Marker marker, String format, Object... argArray) { + if (isErrorEnabled(marker)) { + log(LogLevel.ERROR, null, format, argArray); + } + } + + public void error(Marker marker, String msg, Throwable t) { + if (isErrorEnabled(marker)) { + log(LogLevel.ERROR, t, msg); + } + } + +} diff --git a/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/OutputEventListenerBackedLogger.java b/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/OutputEventListenerBackedLogger.java index 408539b939b74..6e6a8d321bf94 100644 --- a/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/OutputEventListenerBackedLogger.java +++ b/subprojects/logging/src/main/java/org/gradle/internal/logging/slf4j/OutputEventListenerBackedLogger.java @@ -17,7 +17,6 @@ package org.gradle.internal.logging.slf4j; import org.gradle.api.logging.LogLevel; -import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; import org.gradle.internal.logging.events.LogEvent; import org.gradle.internal.logging.events.OutputEventListener; @@ -28,7 +27,7 @@ import org.slf4j.helpers.FormattingTuple; import org.slf4j.helpers.MessageFormatter; -public class OutputEventListenerBackedLogger implements Logger { +public class OutputEventListenerBackedLogger extends BuildOperationAwareLogger { private final String name; private final OutputEventListenerBackedLoggerContext context; @@ -129,8 +128,11 @@ public void trace(Marker marker, String msg, Throwable t) { } private void log(LogLevel logLevel, Throwable throwable, String message) { - OperationIdentifier buildOperationId = CurrentBuildOperationRef.instance().getId(); - LogEvent logEvent = new LogEvent(clock.getCurrentTime(), name, logLevel, message, throwable, buildOperationId); + log(logLevel, throwable, message, CurrentBuildOperationRef.instance().getId()); + } + + void log(LogLevel logLevel, Throwable throwable, String message, OperationIdentifier operationIdentifier) { + LogEvent logEvent = new LogEvent(clock.getCurrentTime(), name, logLevel, message, throwable, operationIdentifier); OutputEventListener outputEventListener = context.getOutputEventListener(); try { outputEventListener.onOutput(logEvent); @@ -151,7 +153,6 @@ private void log(LogLevel logLevel, Throwable throwable, String format, Object a private void log(LogLevel logLevel, Throwable throwable, String format, Object[] args) { FormattingTuple tuple = MessageFormatter.arrayFormat(format, args); Throwable loggedThrowable = throwable == null ? tuple.getThrowable() : throwable; - log(logLevel, loggedThrowable, tuple.getMessage()); } @@ -476,4 +477,6 @@ public void error(Marker marker, String msg, Throwable t) { log(LogLevel.ERROR, t, msg); } } + + } diff --git a/subprojects/logging/src/main/java/org/gradle/util/SingleMessageLogger.java b/subprojects/logging/src/main/java/org/gradle/util/SingleMessageLogger.java index 246e79860b52c..7af2969a5b2ae 100644 --- a/subprojects/logging/src/main/java/org/gradle/util/SingleMessageLogger.java +++ b/subprojects/logging/src/main/java/org/gradle/util/SingleMessageLogger.java @@ -17,7 +17,7 @@ package org.gradle.util; import com.google.common.annotations.VisibleForTesting; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.logging.configuration.WarningMode; import org.gradle.internal.Factory; import org.gradle.internal.featurelifecycle.DeprecatedFeatureUsage; diff --git a/subprojects/logging/src/test/groovy/org/gradle/internal/logging/console/DefaultWorkInProgressFormatterTest.groovy b/subprojects/logging/src/test/groovy/org/gradle/internal/logging/console/DefaultWorkInProgressFormatterTest.groovy index a1bb77e261131..8b6351105030a 100644 --- a/subprojects/logging/src/test/groovy/org/gradle/internal/logging/console/DefaultWorkInProgressFormatterTest.groovy +++ b/subprojects/logging/src/test/groovy/org/gradle/internal/logging/console/DefaultWorkInProgressFormatterTest.groovy @@ -28,9 +28,9 @@ class DefaultWorkInProgressFormatterTest extends Specification { def "formats operations"() { given: - def op1 = new ProgressOperation("STATUS_1", "CATEGORY", new OperationIdentifier(1), null) + def op1 = new ProgressOperation("STATUS_1", "VARIANT_CATEGORY", new OperationIdentifier(1), null) def op2 = new ProgressOperation(null, null, new OperationIdentifier(2), op1) - def op3 = new ProgressOperation("STATUS_2", "CATEGORY", new OperationIdentifier(3), op2) + def op3 = new ProgressOperation("STATUS_2", "VARIANT_CATEGORY", new OperationIdentifier(3), op2) expect: statusBarFormatter.format(op3).first().text == "> STATUS_1 > STATUS_2" @@ -40,7 +40,7 @@ class DefaultWorkInProgressFormatterTest extends Specification { def "trims output to one less than the max console width"() { given: - def operation = new ProgressOperation("these are more than 10 characters", "CATEGORY", new OperationIdentifier(1), null) + def operation = new ProgressOperation("these are more than 10 characters", "VARIANT_CATEGORY", new OperationIdentifier(1), null) when: _ * consoleMetaData.getCols() >> 10 diff --git a/subprojects/logging/src/test/groovy/org/gradle/internal/logging/console/ProgressOperationTest.groovy b/subprojects/logging/src/test/groovy/org/gradle/internal/logging/console/ProgressOperationTest.groovy index 9fef606f936bf..2b17f3dbfce35 100644 --- a/subprojects/logging/src/test/groovy/org/gradle/internal/logging/console/ProgressOperationTest.groovy +++ b/subprojects/logging/src/test/groovy/org/gradle/internal/logging/console/ProgressOperationTest.groovy @@ -22,7 +22,7 @@ class ProgressOperationTest extends Specification { def "message prefers status"() { given: - ProgressOperation progressOperation = new ProgressOperation("STATUS", "CATEGORY", new OperationIdentifier(1), null) + ProgressOperation progressOperation = new ProgressOperation("STATUS", "VARIANT_CATEGORY", new OperationIdentifier(1), null) expect: progressOperation.getMessage() == "STATUS" @@ -30,7 +30,7 @@ class ProgressOperationTest extends Specification { def "message is null if all inputs are null"() { given: - ProgressOperation progressOperation = new ProgressOperation(null, "CATEGORY", new OperationIdentifier(1), null) + ProgressOperation progressOperation = new ProgressOperation(null, "VARIANT_CATEGORY", new OperationIdentifier(1), null) expect: progressOperation.getMessage() == null @@ -38,7 +38,7 @@ class ProgressOperationTest extends Specification { def "allows children to be managed"() { given: - ProgressOperation progressOperation = new ProgressOperation("STATUS", "CATEGORY", new OperationIdentifier(1), null) + ProgressOperation progressOperation = new ProgressOperation("STATUS", "VARIANT_CATEGORY", new OperationIdentifier(1), null) def mockOperation = Mock(ProgressOperation) when: diff --git a/subprojects/logging/src/test/groovy/org/gradle/internal/logging/sink/OutputEventRendererTest.groovy b/subprojects/logging/src/test/groovy/org/gradle/internal/logging/sink/OutputEventRendererTest.groovy index 4a1538fa375cb..8a524eaf031ea 100644 --- a/subprojects/logging/src/test/groovy/org/gradle/internal/logging/sink/OutputEventRendererTest.groovy +++ b/subprojects/logging/src/test/groovy/org/gradle/internal/logging/sink/OutputEventRendererTest.groovy @@ -286,9 +286,7 @@ class OutputEventRendererTest extends OutputSpecification { def rendersLogEventsWhenStdOutAndStdErrAreConsole() { def snapshot = renderer.snapshot() - metaData.stdOut >> true - metaData.stdErr >> true - renderer.addRichConsole(console, metaData) + renderer.addRichConsoleWithErrorOutputOnStdout(console, metaData, true) when: renderer.onOutput(start(description: 'description', buildOperationStart: true, id: 1L, buildOperationId: 1L, buildOperationCategory: BuildOperationCategory.TASK)) @@ -299,14 +297,33 @@ class OutputEventRendererTest extends OutputSpecification { then: console.buildOutputArea.toString().readLines() == ['', '{header}> description{info} status{normal}', 'info', '{error}error', '{normal}'] + outputs.stdOut == '' + outputs.stdErr == '' + } + + def rendersLogEventsWhenStdOutAndStdErrAreSeparateConsoles() { + def snapshot = renderer.snapshot() + def stderrConsole = new ConsoleStub() + renderer.addRichConsole(console, stderrConsole, metaData, true) + + when: + renderer.onOutput(start(description: 'description', buildOperationStart: true, id: 1L, buildOperationId: 1L, buildOperationCategory: BuildOperationCategory.TASK)) + renderer.onOutput(event('info', LogLevel.INFO, 1L)) + renderer.onOutput(event('error', LogLevel.ERROR, 1L)) + renderer.onOutput(complete('status')) + renderer.restore(snapshot) // close console to flush + + then: + console.buildOutputArea.toString().readLines() == ['', '{header}> description{info} status{normal}', 'info'] + stderrConsole.buildOutputArea.toString().readLines() == ['{error}error', '{normal}'] + outputs.stdOut == '' + outputs.stdErr == '' } def rendersLogEventsWhenOnlyStdOutIsConsole() { def snapshot = renderer.snapshot() - metaData.stdOut >> true - metaData.stdErr >> false renderer.attachSystemOutAndErr() - renderer.addRichConsole(console, metaData) + renderer.addRichConsole(console, outputs.stdErrPrintStream, metaData, true) when: renderer.onOutput(start(description: 'description', buildOperationStart: true, id: 1L, buildOperationId: 1L, buildOperationCategory: BuildOperationCategory.TASK)) @@ -317,17 +334,17 @@ class OutputEventRendererTest extends OutputSpecification { then: console.buildOutputArea.toString().readLines() == ['', '{header}> description{info} status{normal}', 'info'] + outputs.stdOut == '' + outputs.stdErr.readLines() == ['error'] } def rendersLogEventsWhenOnlyStdErrIsConsole() { def snapshot = renderer.snapshot() - metaData.stdOut >> false - metaData.stdErr >> true renderer.attachSystemOutAndErr() - renderer.addRichConsole(console, metaData) + renderer.addRichConsole(outputs.stdOutPrintStream, console, true) when: - renderer.onOutput(start('description')) + renderer.onOutput(start(description: 'description', buildOperationStart: true, id: 1L, buildOperationId: 1L, buildOperationCategory: BuildOperationCategory.TASK)) renderer.onOutput(event('info', LogLevel.INFO)) renderer.onOutput(event('error', LogLevel.ERROR)) renderer.onOutput(complete('status')) @@ -335,14 +352,14 @@ class OutputEventRendererTest extends OutputSpecification { then: console.buildOutputArea.toString().readLines() == ['{error}error', '{normal}'] + outputs.stdOut.readLines() == ['info', '> description status'] + outputs.stdErr == '' } def rendersLogEventsInConsoleWhenLogLevelIsDebug() { renderer.configure(LogLevel.DEBUG) def snapshot = renderer.snapshot() - metaData.stdOut >> true - metaData.stdErr >> true - renderer.addRichConsole(console, metaData) + renderer.addRichConsoleWithErrorOutputOnStdout(console, metaData, false) when: renderer.onOutput(event(tenAm, 'info', LogLevel.INFO)) @@ -351,15 +368,15 @@ class OutputEventRendererTest extends OutputSpecification { then: console.buildOutputArea.toString().readLines() == ['10:00:00.000 [INFO] [category] info', '{error}10:00:00.000 [ERROR] [category] error', '{normal}'] + outputs.stdOut == '' + outputs.stdErr == '' } def attachesConsoleWhenStdOutAndStdErrAreAttachedToConsole() { when: renderer.attachSystemOutAndErr() def snapshot = renderer.snapshot() - metaData.stdOut >> true - metaData.stdErr >> true - renderer.addRichConsole(console, metaData) + renderer.addRichConsoleWithErrorOutputOnStdout(console, metaData, false) renderer.onOutput(event('info', LogLevel.INFO)) renderer.onOutput(event('error', LogLevel.ERROR)) renderer.restore(snapshot) // close console to flush @@ -374,9 +391,7 @@ class OutputEventRendererTest extends OutputSpecification { when: renderer.attachSystemOutAndErr() def snapshot = renderer.snapshot() - metaData.stdOut >> true - metaData.stdErr >> false - renderer.addRichConsole(console, metaData) + renderer.addRichConsole(console, outputs.stdErrPrintStream, metaData, false) renderer.onOutput(event('info', LogLevel.INFO)) renderer.onOutput(event('error', LogLevel.ERROR)) renderer.restore(snapshot) // close console to flush @@ -391,9 +406,7 @@ class OutputEventRendererTest extends OutputSpecification { when: renderer.attachSystemOutAndErr() def snapshot = renderer.snapshot() - metaData.stdOut >> false - metaData.stdErr >> true - renderer.addRichConsole(console, metaData) + renderer.addRichConsole(outputs.stdOutPrintStream, console, false) renderer.onOutput(event('info', LogLevel.INFO)) renderer.onOutput(event('error', LogLevel.ERROR)) renderer.restore(snapshot) // close console to flush diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishConsoleIntegrationTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishConsoleIntegrationTest.groovy index 5f9610317a1ac..dc6b433b071a8 100644 --- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishConsoleIntegrationTest.groovy +++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishConsoleIntegrationTest.groovy @@ -77,7 +77,7 @@ class MavenPublishConsoleIntegrationTest extends AbstractMavenPublishIntegTest i then: ConcurrentTestUtil.poll { - assert build.standardOutput.contains(workInProgressLine("> :publishMavenPublicationToMavenRepository > test-1.2.jar")) + assertHasWorkInProgress(build, "> :publishMavenPublicationToMavenRepository > test-1.2.jar") } when: @@ -86,7 +86,7 @@ class MavenPublishConsoleIntegrationTest extends AbstractMavenPublishIntegTest i then: ConcurrentTestUtil.poll { - assert build.standardOutput.contains(workInProgressLine("> :publishMavenPublicationToMavenRepository > test-1.2.jar.sha1")) + assertHasWorkInProgress(build, "> :publishMavenPublicationToMavenRepository > test-1.2.jar.sha1") } when: @@ -95,7 +95,7 @@ class MavenPublishConsoleIntegrationTest extends AbstractMavenPublishIntegTest i then: ConcurrentTestUtil.poll { - assert build.standardOutput.contains(workInProgressLine("> :publishMavenPublicationToMavenRepository > test-1.2.pom")) + assertHasWorkInProgress(build, "> :publishMavenPublicationToMavenRepository > test-1.2.pom") } when: @@ -104,7 +104,7 @@ class MavenPublishConsoleIntegrationTest extends AbstractMavenPublishIntegTest i then: ConcurrentTestUtil.poll { - assert build.standardOutput.contains(workInProgressLine("> :publishMavenPublicationToMavenRepository > test-1.2.pom.sha1")) + assertHasWorkInProgress(build, "> :publishMavenPublicationToMavenRepository > test-1.2.pom.sha1") } when: @@ -113,7 +113,7 @@ class MavenPublishConsoleIntegrationTest extends AbstractMavenPublishIntegTest i then: ConcurrentTestUtil.poll { - assert build.standardOutput.contains(workInProgressLine("> :publishMavenPublicationToMavenRepository > test-1.2.module > 1 KB/1 KB uploaded")) + assertHasWorkInProgress(build, "> :publishMavenPublicationToMavenRepository > test-1.2.module > 1 KB/1 KB uploaded") } when: @@ -123,7 +123,7 @@ class MavenPublishConsoleIntegrationTest extends AbstractMavenPublishIntegTest i then: ConcurrentTestUtil.poll { // TODO - where did this one go? -// assert build.standardOutput.contains(workInProgressLine("> :publishMavenPublicationToMavenRepository > maven-metadata.xml")) +// assertHasWorkInProgress(build, "> :publishMavenPublicationToMavenRepository > maven-metadata.xml") } when: @@ -132,7 +132,7 @@ class MavenPublishConsoleIntegrationTest extends AbstractMavenPublishIntegTest i then: ConcurrentTestUtil.poll { - assert build.standardOutput.contains(workInProgressLine("> :publishMavenPublicationToMavenRepository > maven-metadata.xml")) + assertHasWorkInProgress(build, "> :publishMavenPublicationToMavenRepository > maven-metadata.xml") } putMetaData.releaseAll() diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishFeaturesJavaIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishFeaturesJavaIntegTest.groovy index 1c20c9a39d6e9..32f4a1bb414ef 100644 --- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishFeaturesJavaIntegTest.groovy +++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishFeaturesJavaIntegTest.groovy @@ -44,7 +44,10 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava optionalFeatureImplementation 'org:optionaldep:1.0' } - components.java.addVariantsFromConfiguration(configurations.optionalFeatureRuntimeElements, { it.mapToMavenScope('compile', true) }) + components.java.addVariantsFromConfiguration(configurations.optionalFeatureRuntimeElements) { + it.mapToMavenScope('compile') + it.mapToOptional() + } """ when: @@ -124,8 +127,14 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava optionalFeature2Implementation 'org:optionaldep2-g2:1.0' } - components.java.addVariantsFromConfiguration(configurations.optionalFeature1RuntimeElements, { it.mapToMavenScope('compile', true) }) - components.java.addVariantsFromConfiguration(configurations.optionalFeature2RuntimeElements, { it.mapToMavenScope('compile', true) }) + components.java.addVariantsFromConfiguration(configurations.optionalFeature1RuntimeElements) { + it.mapToMavenScope('compile') + it.mapToOptional() + } + components.java.addVariantsFromConfiguration(configurations.optionalFeature2RuntimeElements) { + it.mapToMavenScope('compile') + it.mapToOptional() + } """ when: @@ -185,10 +194,17 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava optionalFeatureImplementation 'org:optionaldep:1.0' } - components.java.addVariantsFromConfiguration(configurations.optionalFeatureRuntimeElements, { it.mapToMavenScope('compile', true) }) + components.java.addVariantsFromConfiguration(configurations.optionalFeatureRuntimeElements) { + it.mapToMavenScope('compile') + it.mapToOptional() + } - artifacts { - optionalFeatureRuntimeElements file:file("\$buildDir/$optionalFeatureFileName"), builtBy:'touchFile' + artifacts { + if ('$classifier' == 'null') { + optionalFeatureRuntimeElements file:file("\$buildDir/$optionalFeatureFileName"), builtBy:'touchFile' + } else { + optionalFeatureRuntimeElements file:file("\$buildDir/$optionalFeatureFileName"), builtBy:'touchFile', classifier: '$classifier' + } } task touchFile { @@ -212,7 +228,7 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava javaLibrary.withClassifiedArtifact("optional-feature", "jar") javaLibrary.mavenModule.assertArtifactsPublished( "publishTest-1.9.jar" , - optionalFeatureFileName , + "publishTest-1.9-optional-feature.jar" , "publishTest-1.9.pom", "publishTest-1.9.module") javaLibrary.parsedModuleMetadata.variant("apiElements") { @@ -224,7 +240,7 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava noMoreDependencies() } javaLibrary.parsedModuleMetadata.variant("optionalFeatureRuntimeElements") { - assert files*.name == [optionalFeatureFileName] + assert files*.name == ["publishTest-1.9-optional-feature.jar"] dependency('org', 'optionaldep', '1.0') noMoreDependencies() } @@ -236,7 +252,7 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava resolveRuntimeArtifacts(javaLibrary) { optionalFeatureCapabilities << "org:optional-feature:1.0" withModuleMetadata { - expectFiles "publishTest-1.9.jar", "optionaldep-1.0.jar", optionalFeatureFileName + expectFiles "publishTest-1.9.jar", "optionaldep-1.0.jar", "publishTest-1.9-optional-feature.jar" } withoutModuleMetadata { shouldFail { @@ -248,10 +264,13 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava } where: - id | optionalFeatureFileName | failureText - "with a classifier" | "publishTest-1.9-optional-feature.jar" | null - "with an arbitrary name" | "optional-feature-1.9.jar" | "Invalid publication 'maven': multiple artifacts with the identical extension and classifier ('jar', 'null')" - "with the same name " | "publishTest-1.9.jar" | "Invalid publication 'maven': multiple artifacts with the identical extension and classifier ('jar', 'null')" + id | optionalFeatureFileName | classifier | failureText + "with the same name and an implicit classifier" | "publishTest-1.9-optional-feature.jar" | null | null + "with an arbitrary name and no classifier" | "optional-feature-1.9.jar" | null | "Invalid publication 'maven': multiple artifacts with the identical extension and classifier ('jar', 'null')" + "with an arbitrary name and a classifier" | "other-1.9.jar" | "optional-feature" | null + "with an arbitrary name with an implicit classifier" | "other-1.9-optional-feature.jar" | null | null + "with the same name and no classifier" | "publishTest-1.9.jar" | null | "Invalid publication 'maven': multiple artifacts with the identical extension and classifier ('jar', 'null')" + "with the same name and a classifier" | "publishTest-1.9.jar" | "optional-feature" | null } @@ -278,7 +297,10 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava optionalFeatureImplementation 'org:optionaldep:1.0' } - components.java.addVariantsFromConfiguration(configurations.optionalFeatureRuntimeElements, { it.mapToMavenScope('compile', true) }) + components.java.addVariantsFromConfiguration(configurations.optionalFeatureRuntimeElements) { + it.mapToMavenScope('compile') + it.mapToOptional() + } def alt = configurations.optionalFeatureRuntimeElements.outgoing.variants.create("alternate") alt.attributes { @@ -359,7 +381,8 @@ class MavenPublishFeaturesJavaIntegTest extends AbstractMavenPublishFeaturesJava if (it.configurationVariant.name != 'alternate') { it.skip() } else { - it.mapToMavenScope('compile', true) + it.mapToMavenScope('compile') + it.mapToOptional() } } diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy index a97c5a216c7cb..dffa300f123c6 100644 --- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy +++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishJavaIntegTest.groovy @@ -16,7 +16,7 @@ package org.gradle.api.publish.maven -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport +import org.gradle.api.attributes.Category import org.gradle.api.publish.maven.internal.publication.DefaultMavenPublication import org.gradle.integtests.fixtures.FeaturePreviewsFixture import org.gradle.integtests.fixtures.publish.maven.AbstractMavenPublishIntegTest @@ -970,7 +970,7 @@ $append javaLibrary.parsedModuleMetadata.variant("apiElements") { dependency('org.test:bar:').exists() dependency('org.test:bom:1.0') { - hasAttribute(PlatformSupport.COMPONENT_CATEGORY.name, PlatformSupport.REGULAR_PLATFORM) + hasAttribute(Category.CATEGORY_ATTRIBUTE.name, Category.REGULAR_PLATFORM) } noMoreDependencies() } @@ -979,7 +979,7 @@ $append javaLibrary.parsedModuleMetadata.variant("runtimeElements") { dependency('org.test:bar:').exists() dependency('org.test:bom:1.0') { - hasAttribute(PlatformSupport.COMPONENT_CATEGORY.name, PlatformSupport.REGULAR_PLATFORM) + hasAttribute(Category.CATEGORY_ATTRIBUTE.name, Category.REGULAR_PLATFORM) } noMoreDependencies() } @@ -1144,7 +1144,7 @@ include(':platform') javaLibrary.parsedModuleMetadata.variant("apiElements") { dependency('org.test:bar:').exists() dependency('org.gradle.test:platform:1.9') { - hasAttribute(PlatformSupport.COMPONENT_CATEGORY.name, PlatformSupport.REGULAR_PLATFORM) + hasAttribute(Category.CATEGORY_ATTRIBUTE.name, Category.REGULAR_PLATFORM) } noMoreDependencies() } @@ -1153,7 +1153,7 @@ include(':platform') javaLibrary.parsedModuleMetadata.variant("runtimeElements") { dependency('org.test:bar:').exists() dependency('org.gradle.test:platform:1.9') { - hasAttribute(PlatformSupport.COMPONENT_CATEGORY.name, PlatformSupport.REGULAR_PLATFORM) + hasAttribute(Category.CATEGORY_ATTRIBUTE.name, Category.REGULAR_PLATFORM) } noMoreDependencies() } diff --git a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy index 9f64c2715f97b..ce70982062dc1 100644 --- a/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy +++ b/subprojects/maven/src/integTest/groovy/org/gradle/api/publish/maven/MavenPublishMultiProjectIntegTest.groovy @@ -20,6 +20,7 @@ import org.gradle.integtests.fixtures.executer.GradleContextualExecuter import org.gradle.integtests.fixtures.publish.maven.AbstractMavenPublishIntegTest import spock.lang.IgnoreIf import spock.lang.Issue +import spock.lang.Unroll import java.util.concurrent.atomic.AtomicInteger @@ -386,13 +387,14 @@ project(":project2") { } } - def "publish and resolve java-library with dependency on java-platform"() { + @Unroll + def "publish and resolve java-library with dependency on java-platform (named #platformName)"() { given: javaLibrary(mavenRepo.module("org.test", "foo", "1.0")).withModuleMetadata().publish() javaLibrary(mavenRepo.module("org.test", "bar", "1.1")).withModuleMetadata().publish() settingsFile << """ -include "platform", "library" +include "$platformName", "library" """ buildFile << """ @@ -403,7 +405,7 @@ allprojects { version = "1.0" } -project(":platform") { +project(":$platformName") { apply plugin: 'java-platform' javaPlatform { @@ -430,7 +432,7 @@ project(":library") { apply plugin: 'java-library' dependencies { - api platform(project(":platform")) + api platform(project(":$platformName")) api "org.test:bar" } publishing { @@ -446,7 +448,7 @@ project(":library") { when: run "publish" - def platformModule = mavenRepo.module("org.test", "platform", "1.0").removeGradleMetadataRedirection() + def platformModule = mavenRepo.module("org.test", platformName, "1.0").removeGradleMetadataRedirection() def libraryModule = mavenRepo.module("org.test", "library", "1.0").removeGradleMetadataRedirection() then: @@ -462,10 +464,10 @@ project(":library") { libraryModule.parsedPom.packaging == null libraryModule.parsedPom.scopes.compile.assertDependsOn("org.test:bar:") libraryModule.parsedPom.scopes.compile.assertDependencyManagement() - libraryModule.parsedPom.scopes['import'].expectDependencyManagement("org.test:platform:1.0").hasType('pom') + libraryModule.parsedPom.scopes['import'].expectDependencyManagement("org.test:$platformName:1.0").hasType('pom') libraryModule.parsedModuleMetadata.variant('apiElements') { dependency("org.test:bar:").exists() - dependency("org.test:platform:1.0").exists() + dependency("org.test:$platformName:1.0").exists() noMoreDependencies() } @@ -481,6 +483,9 @@ project(":library") { expectFiles 'bar-1.1.jar', 'library-1.0.jar' } } + + where: + platformName << ['platform', 'aplatform'] } private void createBuildScripts(String append = "") { diff --git a/subprojects/maven/src/main/java/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java b/subprojects/maven/src/main/java/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java index 24960a5f8e542..638e6a12acf88 100644 --- a/subprojects/maven/src/main/java/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java +++ b/subprojects/maven/src/main/java/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublication.java @@ -54,7 +54,6 @@ import org.gradle.api.internal.component.UsageContext; import org.gradle.api.internal.file.FileCollectionFactory; import org.gradle.api.internal.java.JavaLibraryPlatform; -import org.gradle.api.internal.java.usagecontext.FeatureConfigurationUsageContext; import org.gradle.api.internal.project.ProjectInternal; import org.gradle.api.logging.Logger; import org.gradle.api.logging.Logging; @@ -349,8 +348,8 @@ private List getSortedUsageContexts() { } private Set dependenciesFor(UsageContext usage) { - if (usage instanceof FeatureConfigurationUsageContext) { - MavenPublishingAwareContext.ScopeMapping mapping = ((FeatureConfigurationUsageContext) usage).getScopeMapping(); + if (usage instanceof MavenPublishingAwareContext) { + MavenPublishingAwareContext.ScopeMapping mapping = ((MavenPublishingAwareContext) usage).getScopeMapping(); switch (mapping) { case compile: return apiDependencies; @@ -371,8 +370,8 @@ private Set dependenciesFor(UsageContext usage) { } private Set dependencyConstraintsFor(UsageContext usage) { - if (usage instanceof FeatureConfigurationUsageContext) { - MavenPublishingAwareContext.ScopeMapping mapping = ((FeatureConfigurationUsageContext) usage).getScopeMapping(); + if (usage instanceof MavenPublishingAwareContext) { + MavenPublishingAwareContext.ScopeMapping mapping = ((MavenPublishingAwareContext) usage).getScopeMapping(); switch (mapping) { case compile: case compile_optional: diff --git a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy index 0deab6d9f445f..44a9680f2b8a8 100644 --- a/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy +++ b/subprojects/maven/src/test/groovy/org/gradle/api/publish/maven/internal/publication/DefaultMavenPublicationTest.groovy @@ -24,12 +24,12 @@ import org.gradle.api.artifacts.ModuleDependency import org.gradle.api.artifacts.ModuleVersionIdentifier import org.gradle.api.artifacts.ProjectDependency import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.attributes.Category import org.gradle.api.component.ComponentWithVariants import org.gradle.api.file.FileCollection import org.gradle.api.internal.CollectionCallbackActionDecorator import org.gradle.api.internal.FeaturePreviews import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyPublicationResolver import org.gradle.api.internal.attributes.ImmutableAttributes import org.gradle.api.internal.component.SoftwareComponentInternal @@ -532,6 +532,6 @@ class DefaultMavenPublicationTest extends Specification { } def platformAttribute() { - return AttributeTestUtil.attributes([(PlatformSupport.COMPONENT_CATEGORY.name) : PlatformSupport.REGULAR_PLATFORM]) + return AttributeTestUtil.attributesFactory().of(Category.CATEGORY_ATTRIBUTE, TestUtil.objectFactory().named(Category, Category.REGULAR_PLATFORM)) } } diff --git a/subprojects/maven/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/maven/AbstractMavenPublishIntegTest.groovy b/subprojects/maven/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/maven/AbstractMavenPublishIntegTest.groovy index 8e43e945082ab..f3b3389ced2b5 100644 --- a/subprojects/maven/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/maven/AbstractMavenPublishIntegTest.groovy +++ b/subprojects/maven/src/testFixtures/groovy/org/gradle/integtests/fixtures/publish/maven/AbstractMavenPublishIntegTest.groovy @@ -132,7 +132,7 @@ abstract class AbstractMavenPublishIntegTest extends AbstractIntegrationSpec imp dependencies { attributesSchema { - getMatchingStrategy(PlatformSupport.COMPONENT_CATEGORY) + getMatchingStrategy(Category.CATEGORY_ATTRIBUTE) .disambiguationRules .add(PlatformSupport.PreferRegularPlatform) } diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/CollectionPropertyIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/CollectionPropertyIntegrationTest.groovy index 3d63928f4151b..ac9d01caaa561 100644 --- a/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/CollectionPropertyIntegrationTest.groovy +++ b/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/CollectionPropertyIntegrationTest.groovy @@ -40,6 +40,31 @@ class CollectionPropertyIntegrationTest extends AbstractIntegrationSpec { """ } + def "can define task with abstract ListProperty getter"() { + given: + buildFile << """ + abstract class ATask extends DefaultTask { + @Input + abstract ListProperty getProp() + + @TaskAction + void go() { + println("prop = \${prop.get()}") + } + } + + tasks.create("thing", ATask) { + prop = ["a", "b", "c"] + } + """ + + when: + succeeds("thing") + + then: + outputContains("prop = [a, b, c]") + } + def "can finalize the value of a property using API"() { given: buildFile << """ diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/MapPropertyIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/MapPropertyIntegrationTest.groovy index 3fb40f12dbfdb..35bd83a30dd23 100644 --- a/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/MapPropertyIntegrationTest.groovy +++ b/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/MapPropertyIntegrationTest.groovy @@ -61,6 +61,31 @@ class MapPropertyIntegrationTest extends AbstractIntegrationSpec { ''' } + def "can define task with abstract MapProperty getter"() { + given: + buildFile << """ + abstract class MyTask extends DefaultTask { + @Input + abstract MapProperty getProp() + + @TaskAction + void go() { + println("prop = \${prop.get()}") + } + } + + tasks.create("thing", MyTask) { + prop = [a: 12, b: 4] + } + """ + + when: + succeeds("thing") + + then: + outputContains("prop = [a:12, b:4]") + } + def "can finalize the value of a property using API"() { given: buildFile << ''' diff --git a/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/PropertyIntegrationTest.groovy b/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/PropertyIntegrationTest.groovy index a07320aac3931..bd8c3153dfd19 100644 --- a/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/PropertyIntegrationTest.groovy +++ b/subprojects/model-core/src/integTest/groovy/org/gradle/api/provider/PropertyIntegrationTest.groovy @@ -60,6 +60,31 @@ task thing(type: SomeTask) { skipped(":thing") } + def "can define task with abstract Property getter"() { + given: + buildFile << """ + abstract class MyTask extends DefaultTask { + @Input + abstract Property getProp() + + @TaskAction + void go() { + println("prop = \${prop.get()}") + } + } + + tasks.create("thing", MyTask) { + prop = "abc" + } + """ + + when: + succeeds("thing") + + then: + outputContains("prop = abc") + } + def "can finalize the value of a property using API"() { given: buildFile << """ diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/model/NamedObjectInstantiator.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/model/NamedObjectInstantiator.java index 4ce1da39faf5a..86200508119ec 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/model/NamedObjectInstantiator.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/model/NamedObjectInstantiator.java @@ -28,7 +28,7 @@ import org.gradle.internal.Factory; import org.gradle.internal.UncheckedException; import org.gradle.internal.classloader.VisitableURLClassLoader; -import org.gradle.internal.instantiation.Managed; +import org.gradle.internal.state.Managed; import org.gradle.model.internal.asm.AsmClassGenerator; import org.gradle.model.internal.inspect.FormattingValidationProblemCollector; import org.gradle.model.internal.inspect.ValidationProblemCollector; @@ -106,6 +106,9 @@ public T named(final Class type, final String name) throws @Override public T fromState(Class type, Object state) { + if (!Named.class.isAssignableFrom(type)) { + return null; + } return named(Cast.uncheckedCast(type), (String) state); } diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/plugins/DslObject.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/plugins/DslObject.java index 06b20ca16f522..dfb193caea965 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/plugins/DslObject.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/plugins/DslObject.java @@ -16,17 +16,17 @@ package org.gradle.api.internal.plugins; +import org.gradle.api.internal.ConventionMapping; +import org.gradle.api.internal.DynamicObjectAware; import org.gradle.api.internal.GeneratedSubclasses; import org.gradle.api.internal.HasConvention; +import org.gradle.api.internal.IConventionAware; import org.gradle.api.plugins.Convention; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.ExtensionContainer; import org.gradle.api.reflect.HasPublicType; import org.gradle.api.reflect.TypeOf; -import org.gradle.api.internal.ConventionMapping; -import org.gradle.api.internal.IConventionAware; import org.gradle.internal.metaobject.DynamicObject; -import org.gradle.api.internal.DynamicObjectAware; import static org.gradle.internal.Cast.uncheckedCast; @@ -88,15 +88,11 @@ public TypeOf getPublicType() { if (object instanceof HasPublicType) { return uncheckedCast(((HasPublicType) object).getPublicType()); } - return TypeOf.typeOf(firstNonGeneratedClassOf(object.getClass())); + return TypeOf.typeOf(GeneratedSubclasses.unpackType(object)); } public Class getImplementationType() { - return firstNonGeneratedClassOf(object.getClass()); - } - - private Class firstNonGeneratedClassOf(Class clazz) { - return GeneratedSubclasses.unpack(clazz); + return GeneratedSubclasses.unpackType(object); } private static T toType(Object delegate, Class type) { diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/AbstractMinimalProvider.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/AbstractMinimalProvider.java index 1eca309373ce7..68996efd79dbb 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/AbstractMinimalProvider.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/AbstractMinimalProvider.java @@ -19,12 +19,28 @@ import org.gradle.api.Transformer; import org.gradle.api.internal.tasks.TaskDependencyResolveContext; import org.gradle.api.provider.Provider; +import org.gradle.internal.state.Managed; import org.gradle.util.GUtil; import javax.annotation.Nullable; import java.util.Collection; -public abstract class AbstractMinimalProvider implements ProviderInternal { +public abstract class AbstractMinimalProvider implements ProviderInternal, Managed { + private static final Factory FACTORY = new Factory() { + @Nullable + @Override + public T fromState(Class type, Object state) { + if (!type.isAssignableFrom(Provider.class)) { + return null; + } + if (state == null) { + return type.cast(Providers.notDefined()); + } else { + return type.cast(Providers.of(state)); + } + } + }; + @Override public ProviderInternal map(final Transformer transformer) { return new TransformBackedProvider(transformer, this); @@ -85,6 +101,26 @@ public String toString() { return String.format("provider(%s)", GUtil.elvis(getType(), "?")); } + @Override + public boolean immutable() { + return false; + } + + @Override + public Class publicType() { + return Provider.class; + } + + @Override + public Factory managedFactory() { + return FACTORY; + } + + @Override + public Object unpackState() { + return getOrNull(); + } + private static class FlatMapProvider extends AbstractMinimalProvider { private final Provider provider; private final Transformer, ? super T> transformer; diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultListProperty.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultListProperty.java index 3015b8b2127fb..483777e0bc098 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultListProperty.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultListProperty.java @@ -20,6 +20,7 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Provider; +import javax.annotation.Nullable; import java.util.Collection; import java.util.List; @@ -28,6 +29,27 @@ public DefaultListProperty(Class elementType) { super(List.class, elementType); } + @Override + public Class publicType() { + return ListProperty.class; + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Nullable + @Override + public S fromState(Class type, Object state) { + if (!type.isAssignableFrom(ListProperty.class)) { + return null; + } + DefaultListProperty property = new DefaultListProperty<>(DefaultListProperty.this.getElementType()); + property.set((List) state); + return type.cast(property); + } + }; + } + @Override protected List fromValue(Collection values) { return ImmutableList.copyOf(values); diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultMapProperty.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultMapProperty.java index 732b51cc28e31..3178fc3daa9c4 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultMapProperty.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultMapProperty.java @@ -72,6 +72,27 @@ public Class getValueType() { return valueType; } + @Override + public Class publicType() { + return MapProperty.class; + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Nullable + @Override + public S fromState(Class type, Object state) { + if (!type.isAssignableFrom(MapProperty.class)) { + return null; + } + DefaultMapProperty property = new DefaultMapProperty<>(DefaultMapProperty.this.keyType, DefaultMapProperty.this.valueType); + property.set((Map) state); + return type.cast(property); + } + }; + } + @Override public boolean isPresent() { beforeRead(); diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultPropertyState.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultPropertyState.java index 855f31e1988e6..902228d3074e0 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultPropertyState.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultPropertyState.java @@ -20,6 +20,8 @@ import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; +import javax.annotation.Nullable; + public class DefaultPropertyState extends AbstractProperty implements Property { private final Class type; private final ValueSanitizer sanitizer; @@ -31,6 +33,25 @@ public DefaultPropertyState(Class type) { this.sanitizer = ValueSanitizers.forType(type); } + @Override + public Class publicType() { + return Property.class; + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Nullable + @Override + public S fromState(Class type, Object state) { + if (!type.isAssignableFrom(Property.class)) { + return null; + } + return type.cast(new DefaultPropertyState(DefaultPropertyState.this.type).value((T) state)); + } + }; + } + @Override public Class getType() { return type; diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultSetProperty.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultSetProperty.java index 9ff99e4b1e572..b7d5ca7ffe5b9 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultSetProperty.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/DefaultSetProperty.java @@ -20,6 +20,7 @@ import org.gradle.api.provider.Provider; import org.gradle.api.provider.SetProperty; +import javax.annotation.Nullable; import java.util.Collection; import java.util.Set; @@ -33,6 +34,27 @@ protected Set fromValue(Collection values) { return ImmutableSet.copyOf(values); } + @Override + public Class publicType() { + return SetProperty.class; + } + + @Override + public Factory managedFactory() { + return new Factory() { + @Nullable + @Override + public S fromState(Class type, Object state) { + if (!type.isAssignableFrom(SetProperty.class)) { + return null; + } + DefaultSetProperty property = new DefaultSetProperty<>(DefaultSetProperty.this.getElementType()); + property.set((Set) state); + return type.cast(property); + } + }; + } + @Override public SetProperty empty() { super.empty(); diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/Providers.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/Providers.java index 62a36b4d91add..4d72200809d7d 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/Providers.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/provider/Providers.java @@ -32,6 +32,11 @@ public Object get() { throw new IllegalStateException(NULL_VALUE); } + @Override + public boolean immutable() { + return true; + } + @Nullable @Override public Class getType() { diff --git a/subprojects/model-core/src/main/java/org/gradle/api/internal/tasks/WorkNodeAction.java b/subprojects/model-core/src/main/java/org/gradle/api/internal/tasks/WorkNodeAction.java index 7ef03e5f5d379..e4e29e60d8be2 100644 --- a/subprojects/model-core/src/main/java/org/gradle/api/internal/tasks/WorkNodeAction.java +++ b/subprojects/model-core/src/main/java/org/gradle/api/internal/tasks/WorkNodeAction.java @@ -17,6 +17,7 @@ package org.gradle.api.internal.tasks; import org.gradle.api.Project; +import org.gradle.internal.service.ServiceRegistry; import javax.annotation.Nullable; @@ -33,5 +34,5 @@ public interface WorkNodeAction { /** * Run the action, throwing any failure. */ - void run(); + void run(ServiceRegistry registry); } diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/AbstractClassGenerator.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/AbstractClassGenerator.java index cd0bd59807045..ab3281fdcf6fe 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/AbstractClassGenerator.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/AbstractClassGenerator.java @@ -25,12 +25,16 @@ import org.gradle.api.Action; import org.gradle.api.NonExtensible; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.internal.DynamicObjectAware; import org.gradle.api.internal.IConventionAware; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.provider.HasMultipleValues; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.internal.Cast; import org.gradle.internal.extensibility.NoConventionMapping; import org.gradle.internal.logging.text.TreeFormatter; @@ -89,8 +93,8 @@ public AbstractClassGenerator(Collection allK this.enabledAnnotations = ImmutableSet.copyOf(enabledAnnotations); ImmutableSet.Builder> builder = ImmutableSet.builder(); for (InjectAnnotationHandler handler : allKnownAnnotations) { - if (!enabledAnnotations.contains(handler.getAnnotation())) { - builder.add(handler.getAnnotation()); + if (!enabledAnnotations.contains(handler.getAnnotationType())) { + builder.add(handler.getAnnotationType()); } } this.disabledAnnotations = builder.build(); @@ -207,7 +211,7 @@ private GeneratedClass generateUnderLock(Class type) { private void inspectType(Class type, List validators, List generationHandlers, UnclaimedPropertyHandler unclaimedHandler) { ClassDetails classDetails = ClassInspector.inspect(type); - ClassMetaData classMetaData = new ClassMetaData(); + ClassMetadata classMetaData = new ClassMetadata(type); assembleProperties(classDetails, classMetaData); for (ClassGenerationHandler handler : generationHandlers) { @@ -221,14 +225,14 @@ private void inspectType(Class type, List validators, List type, List generationH } } - private void assembleProperties(ClassDetails classDetails, ClassMetaData classMetaData) { + private void assembleProperties(ClassDetails classDetails, ClassMetadata classMetaData) { for (PropertyDetails property : classDetails.getProperties()) { - PropertyMetaData propertyMetaData = classMetaData.property(property.getName()); + PropertyMetadata propertyMetaData = classMetaData.property(property.getName()); for (Method method : property.getGetters()) { - propertyMetaData.addGetter(method); + propertyMetaData.addGetter(classMetaData.resolveTypeVariables(method)); } for (Method method : property.getSetters()) { propertyMetaData.addSetter(method); @@ -291,7 +295,7 @@ private void assembleProperties(ClassDetails classDetails, ClassMetaData classMe for (Method method : classDetails.getInstanceMethods()) { Class[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { - PropertyMetaData propertyMetaData = classMetaData.getProperty(method.getName()); + PropertyMetadata propertyMetaData = classMetaData.getProperty(method.getName()); if (propertyMetaData != null) { propertyMetaData.addSetMethod(method); } @@ -435,34 +439,81 @@ public GeneratedClassImpl asWrapper() { } } - private static class ClassMetaData { - private final Map properties = new LinkedHashMap(); + private static class ClassMetadata { + private final Class type; + private final Map properties = new LinkedHashMap(); + + public ClassMetadata(Class type) { + this.type = type; + } + + /** + * Determines the concrete return type of the given method, resolving any type parameters. + */ + public MethodMetadata resolveTypeVariables(Method method) { + Type resolvedReturnType = JavaPropertyReflectionUtil.resolveMethodReturnType(type, method); + return new MethodMetadata(method, resolvedReturnType); + } @Nullable - public PropertyMetaData getProperty(String name) { + public PropertyMetadata getProperty(String name) { return properties.get(name); } - public PropertyMetaData property(String name) { - PropertyMetaData property = properties.get(name); + public PropertyMetadata property(String name) { + PropertyMetadata property = properties.get(name); if (property == null) { - property = new PropertyMetaData(name); + property = new PropertyMetadata(name); properties.put(name, property); } return property; } } - protected static class PropertyMetaData { + protected static class MethodMetadata { + private final Method method; + private final Type returnType; + + public MethodMetadata(Method method, Type returnType) { + this.method = method; + this.returnType = returnType; + } + + public String getName() { + return method.getName(); + } + + public boolean isAbstract() { + return Modifier.isAbstract(method.getModifiers()); + } + + boolean shouldOverride() { + return !Modifier.isFinal(method.getModifiers()) && !method.isBridge(); + } + + boolean shouldImplement() { + return !method.isBridge(); + } + + public Class getReturnType() { + return method.getReturnType(); + } + + public Type getGenericReturnType() { + return returnType; + } + } + + protected static class PropertyMetadata { private final String name; - private final List getters = new ArrayList(); - private final List overridableGetters = new ArrayList(); + private final List getters = new ArrayList<>(); + private final List overridableGetters = new ArrayList<>(); private final List overridableSetters = new ArrayList(); private final List setters = new ArrayList(); private final List setMethods = new ArrayList(); - private Method mainGetter; + private MethodMetadata mainGetter; - private PropertyMetaData(String name) { + private PropertyMetadata(String name) { this.name = name; } @@ -479,7 +530,11 @@ public boolean isReadable() { return mainGetter != null; } - public List getOverridableGetters() { + public MethodMetadata getMainGetter() { + return mainGetter; + } + + public List getOverridableGetters() { return overridableGetters; } @@ -501,15 +556,15 @@ public Type getGenericType() { return setters.get(0).getGenericParameterTypes()[0]; } - public void addGetter(Method method) { - if (!Modifier.isFinal(method.getModifiers()) && !method.isBridge()) { - overridableGetters.add(method); + public void addGetter(MethodMetadata metadata) { + if (metadata.shouldOverride()) { + overridableGetters.add(metadata); } - getters.add(method); + getters.add(metadata); if (mainGetter == null) { - mainGetter = method; - } else if (mainGetter.isBridge() && !method.isBridge()) { - mainGetter = method; + mainGetter = metadata; + } else if (!mainGetter.shouldImplement() && metadata.shouldImplement()) { + mainGetter = metadata; } } @@ -547,7 +602,7 @@ void visitInstanceMethod(Method method) { /** * Collect information about a property. This is called for all properties of a type. */ - void visitProperty(PropertyMetaData property) { + void visitProperty(PropertyMetadata property) { } /** @@ -560,14 +615,14 @@ public void hasFields() { * Handler can claim the property, taking responsibility for generating whatever is required to make the property work. * Handler is also expected to take care of validation. */ - boolean claimProperty(PropertyMetaData property) { + boolean claimPropertyImplementation(PropertyMetadata property) { return false; } /** * Called when another a handler with higher precedence has also claimed the given property. */ - void ambiguous(PropertyMetaData property) { + void ambiguous(PropertyMetadata property) { // No supposed to happen throw new UnsupportedOperationException("Multiple matches for " + property.getName()); } @@ -583,7 +638,7 @@ private interface UnclaimedPropertyHandler { /** * Called when no handler has claimed the property. */ - void unclaimed(PropertyMetaData property); + void unclaimed(PropertyMetadata property); } private static class DslMixInPropertyType extends ClassGenerationHandler { @@ -591,7 +646,7 @@ private static class DslMixInPropertyType extends ClassGenerationHandler { private boolean providesOwnDynamicObject; private boolean needDynamicAware; private boolean needGroovyObject; - private final List mutableProperties = new ArrayList(); + private final List mutableProperties = new ArrayList(); private final MethodSet actionMethods = new MethodSet(); private final SetMultimap closureMethods = LinkedHashMultimap.create(); @@ -606,7 +661,7 @@ void startType(Class type) { } @Override - void visitProperty(PropertyMetaData property) { + void visitProperty(PropertyMetadata property) { if (property.setters.isEmpty()) { return; } @@ -618,7 +673,7 @@ void visitProperty(PropertyMetaData property) { } @Override - boolean claimProperty(PropertyMetaData property) { + boolean claimPropertyImplementation(PropertyMetadata property) { if (property.getName().equals("asDynamicObject")) { providesOwnDynamicObject = true; return true; @@ -657,7 +712,7 @@ void applyTo(ClassGenerationVisitor visitor) { } private void addSetMethods(AbstractClassGenerator.ClassGenerationVisitor visitor) { - for (PropertyMetaData property : mutableProperties) { + for (PropertyMetadata property : mutableProperties) { if (property.setMethods.isEmpty()) { for (Method setter : property.setters) { visitor.addSetMethod(property, setter); @@ -705,7 +760,7 @@ private static class ExtensibleTypePropertyHandler extends ClassGenerationHandle private boolean conventionAware; private boolean extensible; private boolean hasExtensionAwareImplementation; - private final List conventionProperties = new ArrayList(); + private final List conventionProperties = new ArrayList(); @Override void startType(Class type) { @@ -723,11 +778,11 @@ void startType(Class type) { } @Override - boolean claimProperty(PropertyMetaData property) { + boolean claimPropertyImplementation(PropertyMetadata property) { if (extensible) { if (property.getName().equals("extensions")) { - for (Method getter : property.getOverridableGetters()) { - if (Modifier.isAbstract(getter.getModifiers())) { + for (MethodMetadata getter : property.getOverridableGetters()) { + if (getter.isAbstract()) { return true; } } @@ -743,9 +798,9 @@ boolean claimProperty(PropertyMetaData property) { } @Override - public void unclaimed(PropertyMetaData property) { - for (Method getter : property.getOverridableGetters()) { - if (!getter.getDeclaringClass().isAssignableFrom(noMappingClass)) { + public void unclaimed(PropertyMetadata property) { + for (MethodMetadata getter : property.getOverridableGetters()) { + if (!getter.method.getDeclaringClass().isAssignableFrom(noMappingClass)) { conventionProperties.add(property); break; } @@ -770,10 +825,10 @@ void applyTo(ClassGenerationVisitor visitor) { if (conventionAware && !IConventionAware.class.isAssignableFrom(type)) { visitor.mixInConventionAware(); } - for (PropertyMetaData property : conventionProperties) { + for (PropertyMetadata property : conventionProperties) { visitor.applyConventionMappingToProperty(property); - for (Method getter : property.getOverridableGetters()) { - visitor.applyConventionMappingToGetter(property, getter); + for (MethodMetadata getter : property.getOverridableGetters()) { + visitor.applyConventionMappingToGetter(property, getter.method); } for (Method setter : property.getOverridableSetters()) { visitor.applyConventionMappingToSetter(property, setter); @@ -783,8 +838,8 @@ void applyTo(ClassGenerationVisitor visitor) { } private static class ManagedTypeHandler extends ClassGenerationHandler { - private final List mutableProperties = new ArrayList<>(); - private final List readOnlyProperties = new ArrayList<>(); + private final List mutableProperties = new ArrayList<>(); + private final List readOnlyProperties = new ArrayList<>(); private boolean hasFields; @Override @@ -793,10 +848,10 @@ public void hasFields() { } @Override - boolean claimProperty(PropertyMetaData property) { + boolean claimPropertyImplementation(PropertyMetadata property) { // Skip properties with non-abstract getter or setter implementations - for (Method getter : property.getters) { - if (!Modifier.isAbstract(getter.getModifiers())) { + for (MethodMetadata getter : property.getters) { + if (!getter.isAbstract()) { return false; } } @@ -808,9 +863,18 @@ boolean claimProperty(PropertyMetaData property) { if (property.getters.isEmpty()) { return false; } + + // Property is readable and all getters and setters are abstract + if (property.setters.isEmpty()) { - if (property.getType().equals(ConfigurableFileCollection.class)) { - // Read-only file collection property + if (property.getType().equals(ConfigurableFileCollection.class) + || property.getType().equals(ListProperty.class) + || property.getType().equals(SetProperty.class) + || property.getType().equals(MapProperty.class) + || property.getType().equals(RegularFileProperty.class) + || property.getType().equals(DirectoryProperty.class) + || property.getType().equals(Property.class)) { + // Read-only property with managed type readOnlyProperties.add(property); return true; } @@ -834,19 +898,19 @@ void applyTo(ClassInspectionVisitor visitor) { @Override void applyTo(ClassGenerationVisitor visitor) { - for (PropertyMetaData property : mutableProperties) { + for (PropertyMetadata property : mutableProperties) { visitor.applyManagedStateToProperty(property); - for (Method getter : property.getters) { - visitor.applyManagedStateToGetter(property, getter); + for (MethodMetadata getter : property.getters) { + visitor.applyManagedStateToGetter(property, getter.method); } for (Method setter : property.setters) { visitor.applyManagedStateToSetter(property, setter); } } - for (PropertyMetaData property : readOnlyProperties) { + for (PropertyMetadata property : readOnlyProperties) { visitor.applyManagedStateToProperty(property); - for (Method getter : property.getters) { - visitor.applyReadOnlyManagedStateToGetter(property, getter); + for (MethodMetadata getter : property.getters) { + visitor.applyReadOnlyManagedStateToGetter(property, getter.method); } } if (!hasFields) { @@ -856,28 +920,26 @@ void applyTo(ClassGenerationVisitor visitor) { } private static class PropertyTypePropertyHandler extends ClassGenerationHandler { - private final List propertyTyped = new ArrayList(); + private final List propertyTyped = new ArrayList(); @Override - boolean claimProperty(PropertyMetaData property) { + void visitProperty(PropertyMetadata property) { if (property.isReadable() && isModelProperty(property)) { propertyTyped.add(property); - return true; } - return false; } @Override void applyTo(ClassGenerationVisitor visitor) { - for (PropertyMetaData property : propertyTyped) { - visitor.addPropertySetters(property, property.mainGetter); + for (PropertyMetadata property : propertyTyped) { + visitor.addPropertySetters(property, property.mainGetter.method); } } - private boolean isModelProperty(PropertyMetaData property) { + private boolean isModelProperty(PropertyMetadata property) { return Property.class.isAssignableFrom(property.getType()) || - HasMultipleValues.class.isAssignableFrom(property.getType()) || - MapProperty.class.isAssignableFrom(property.getType()); + HasMultipleValues.class.isAssignableFrom(property.getType()) || + MapProperty.class.isAssignableFrom(property.getType()); } } @@ -956,7 +1018,7 @@ private static class ServicesPropertyHandler extends ClassGenerationHandler { private boolean hasServicesProperty; @Override - public boolean claimProperty(PropertyMetaData property) { + public boolean claimPropertyImplementation(PropertyMetadata property) { if (property.getName().equals("services") && property.isReadable() && ServiceRegistry.class.isAssignableFrom(property.getType())) { hasServicesProperty = true; return true; @@ -974,16 +1036,16 @@ void applyTo(ClassInspectionVisitor visitor) { private static abstract class AbstractInjectedPropertyHandler extends ClassGenerationHandler { final Class annotation; - final List serviceInjectionProperties = new ArrayList(); + final List serviceInjectionProperties = new ArrayList(); public AbstractInjectedPropertyHandler(Class annotation) { this.annotation = annotation; } @Override - public boolean claimProperty(PropertyMetaData property) { - for (Method method : property.getters) { - if (method.getAnnotation(annotation) != null) { + public boolean claimPropertyImplementation(PropertyMetadata property) { + for (MethodMetadata getter : property.getters) { + if (getter.method.getAnnotation(annotation) != null) { serviceInjectionProperties.add(property); return true; } @@ -992,14 +1054,14 @@ public boolean claimProperty(PropertyMetaData property) { } @Override - void ambiguous(PropertyMetaData property) { - for (Method method : property.getters) { - if (method.getAnnotation(annotation) != null) { + void ambiguous(PropertyMetadata property) { + for (MethodMetadata getter : property.getters) { + if (getter.method.getAnnotation(annotation) != null) { TreeFormatter formatter = new TreeFormatter(); formatter.node("Cannot use "); formatter.appendAnnotation(annotation); formatter.append(" annotation on method "); - formatter.appendMethod(method); + formatter.appendMethod(getter.method); formatter.append("."); throw new IllegalArgumentException(formatter.toString()); } @@ -1016,7 +1078,7 @@ void applyTo(ClassInspectionVisitor visitor) { public List> getInjectedServices() { ImmutableList.Builder> services = ImmutableList.builderWithExpectedSize(serviceInjectionProperties.size()); - for (PropertyMetaData property : serviceInjectionProperties) { + for (PropertyMetadata property : serviceInjectionProperties) { services.add(property.getType()); } return services.build(); @@ -1038,9 +1100,9 @@ public InjectAnnotationPropertyHandler() { @Override public void applyTo(ClassGenerationVisitor visitor) { - for (PropertyMetaData property : serviceInjectionProperties) { + for (PropertyMetadata property : serviceInjectionProperties) { visitor.applyServiceInjectionToProperty(property); - for (Method getter : property.getOverridableGetters()) { + for (MethodMetadata getter : property.getOverridableGetters()) { visitor.applyServiceInjectionToGetter(property, getter); } for (Method setter : property.getOverridableSetters()) { @@ -1057,9 +1119,9 @@ public CustomInjectAnnotationPropertyHandler(Class injectA @Override public void applyTo(ClassGenerationVisitor visitor) { - for (PropertyMetaData property : serviceInjectionProperties) { + for (PropertyMetadata property : serviceInjectionProperties) { visitor.applyServiceInjectionToProperty(property); - for (Method getter : property.getOverridableGetters()) { + for (MethodMetadata getter : property.getOverridableGetters()) { visitor.applyServiceInjectionToGetter(property, annotation, getter); } for (Method setter : property.getOverridableSetters()) { @@ -1122,39 +1184,39 @@ protected interface ClassGenerationVisitor { void addExtensionsProperty(); - void applyServiceInjectionToProperty(PropertyMetaData property); + void applyServiceInjectionToProperty(PropertyMetadata property); - void applyServiceInjectionToGetter(PropertyMetaData property, Method getter); + void applyServiceInjectionToGetter(PropertyMetadata property, MethodMetadata getter); - void applyServiceInjectionToSetter(PropertyMetaData property, Method setter); + void applyServiceInjectionToSetter(PropertyMetadata property, Method setter); - void applyServiceInjectionToGetter(PropertyMetaData property, Class annotation, Method getter); + void applyServiceInjectionToGetter(PropertyMetadata property, Class annotation, MethodMetadata getter); - void applyServiceInjectionToSetter(PropertyMetaData property, Class annotation, Method setter); + void applyServiceInjectionToSetter(PropertyMetadata property, Class annotation, Method setter); - void applyManagedStateToProperty(PropertyMetaData property); + void applyManagedStateToProperty(PropertyMetadata property); - void applyManagedStateToGetter(PropertyMetaData property, Method getter); + void applyManagedStateToGetter(PropertyMetadata property, Method getter); - void applyManagedStateToSetter(PropertyMetaData property, Method setter); + void applyManagedStateToSetter(PropertyMetadata property, Method setter); - void applyReadOnlyManagedStateToGetter(PropertyMetaData property, Method getter); + void applyReadOnlyManagedStateToGetter(PropertyMetadata property, Method getter); - void addManagedMethods(List properties, List readOnlyProperties); + void addManagedMethods(List properties, List readOnlyProperties); - void applyConventionMappingToProperty(PropertyMetaData property); + void applyConventionMappingToProperty(PropertyMetadata property); - void applyConventionMappingToGetter(PropertyMetaData property, Method getter); + void applyConventionMappingToGetter(PropertyMetadata property, Method getter); - void applyConventionMappingToSetter(PropertyMetaData property, Method setter); + void applyConventionMappingToSetter(PropertyMetadata property, Method setter); - void applyConventionMappingToSetMethod(PropertyMetaData property, Method metaMethod); + void applyConventionMappingToSetMethod(PropertyMetadata property, Method metaMethod); - void addSetMethod(PropertyMetaData propertyMetaData, Method setter); + void addSetMethod(PropertyMetadata propertyMetaData, Method setter); void addActionMethod(Method method); - void addPropertySetters(PropertyMetaData property, Method getter); + void addPropertySetters(PropertyMetadata property, Method getter); Class generate() throws Exception; } diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/AsmBackedClassGenerator.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/AsmBackedClassGenerator.java index 6d29a607e47b6..d54bd8a4a98d7 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/AsmBackedClassGenerator.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/AsmBackedClassGenerator.java @@ -26,6 +26,8 @@ import org.gradle.api.Action; import org.gradle.api.Transformer; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.internal.ConventionMapping; import org.gradle.api.internal.DynamicObjectAware; import org.gradle.api.internal.GeneratedSubclass; @@ -36,6 +38,10 @@ import org.gradle.api.plugins.Convention; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; +import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.internal.UncheckedException; import org.gradle.internal.extensibility.ConventionAwareHelper; import org.gradle.internal.logging.text.TreeFormatter; @@ -46,6 +52,7 @@ import org.gradle.internal.reflect.JavaReflectionUtil; import org.gradle.internal.service.ServiceLookup; import org.gradle.internal.service.ServiceRegistry; +import org.gradle.internal.state.Managed; import org.gradle.model.internal.asm.AsmClassGenerator; import org.gradle.util.CollectionUtils; import org.gradle.util.ConfigureUtil; @@ -64,12 +71,14 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Set; +import static org.gradle.model.internal.asm.AsmClassGeneratorUtils.getterSignature; import static org.gradle.model.internal.asm.AsmClassGeneratorUtils.signature; import static org.objectweb.asm.Opcodes.AALOAD; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; @@ -105,22 +114,6 @@ public static ServiceLookup getServicesForNext() { return SERVICES_FOR_NEXT_OBJECT.get().services; } - // Used by generated code - @SuppressWarnings("unused") - public static Instantiator getInstantiatorForNext() { - return createExtensionInstantiator(getServicesForNext(), SERVICES_FOR_NEXT_OBJECT.get().instantiator); - } - - private static Instantiator createExtensionInstantiator(ServiceLookup services, Instantiator nested) { - // The instantiator provided to the generated class is used for implementing `Convention`. - // To maintain backward compatibility, create a 'lenient' instantiator for this purpose, where possible. - InstantiatorFactory instantiatorFactory = (InstantiatorFactory) services.find(InstantiatorFactory.class); - if (instantiatorFactory != null) { - return instantiatorFactory.injectAndDecorateLenient(services); - } - return nested; - } - private AsmBackedClassGenerator(boolean decorate, String suffix, Collection allKnownAnnotations, Collection> enabledAnnotations) { super(allKnownAnnotations, enabledAnnotations); this.decorate = decorate; @@ -245,7 +238,8 @@ public ClassGenerationVisitor builder() { formatter.append(" is final."); throw new ClassGenerationException(formatter.toString()); } - ClassBuilderImpl builder = new ClassBuilderImpl(type, decorate, suffix, extensible, conventionAware, managed, providesOwnDynamicObjectImplementation, serviceInjection && !providesOwnServicesImplementation); + boolean requiresServicesMethod = (extensible || serviceInjection) && !providesOwnServicesImplementation; + ClassBuilderImpl builder = new ClassBuilderImpl(type, decorate, suffix, extensible, conventionAware, managed, providesOwnDynamicObjectImplementation, requiresServicesMethod); builder.startClass(); return builder; } @@ -259,7 +253,6 @@ private static class ClassBuilderImpl implements ClassGenerationVisitor { private static final String META_CLASS_FIELD = "_gr_mc_"; private static final String SERVICES_FIELD = "_gr_svcs_"; private static final String SERVICES_METHOD = "$gradleServices"; - private static final String INSTANTIATOR_FIELD = "_gr_inst_"; private static final String FACTORY_FIELD = "_gr_factory_"; private static final String CONVENTION_MAPPING_FIELD_DESCRIPTOR = Type.getDescriptor(ConventionMapping.class); private static final String META_CLASS_TYPE_DESCRIPTOR = Type.getDescriptor(MetaClass.class); @@ -274,7 +267,7 @@ private static class ClassBuilderImpl implements ClassGenerationVisitor { private final static Type CONVENTION_MAPPING_TYPE = Type.getType(ConventionMapping.class); private final static Type GROOVY_OBJECT_TYPE = Type.getType(GroovyObject.class); private final static Type CONVENTION_TYPE = Type.getType(Convention.class); - private static final Type ASM_BACKED_CLASS_GENERATOR_TYPE = Type.getType(AsmBackedClassGenerator.class); + private final static Type ASM_BACKED_CLASS_GENERATOR_TYPE = Type.getType(AsmBackedClassGenerator.class); private final static Type ABSTRACT_DYNAMIC_OBJECT_TYPE = Type.getType(AbstractDynamicObject.class); private final static Type EXTENSIBLE_DYNAMIC_OBJECT_HELPER_TYPE = Type.getType(MixInExtensibleDynamicObject.class); private final static Type NON_EXTENSIBLE_DYNAMIC_OBJECT_HELPER_TYPE = Type.getType(BeanDynamicObject.class); @@ -296,12 +289,20 @@ private static class ClassBuilderImpl implements ClassGenerationVisitor { private static final Type ACTION_TYPE = Type.getType(Action.class); private static final Type PROPERTY_INTERNAL_TYPE = Type.getType(PropertyInternal.class); private static final Type INSTANTIATOR_TYPE = Type.getType(Instantiator.class); + private static final Type INSTANTIATOR_FACTORY_TYPE = Type.getType(InstantiatorFactory.class); private static final Type OBJECT_FACTORY_TYPE = Type.getType(ObjectFactory.class); private static final Type CONFIGURABLE_FILE_COLLECTION_TYPE = Type.getType(ConfigurableFileCollection.class); private static final Type MANAGED_TYPE = Type.getType(Managed.class); + private static final Type REGULAR_FILE_PROPERTY_TYPE = Type.getType(RegularFileProperty.class); + private static final Type DIRECTORY_PROPERTY_TYPE = Type.getType(DirectoryProperty.class); + private static final Type PROPERTY_TYPE = Type.getType(Property.class); + private static final Type LIST_PROPERTY_TYPE = Type.getType(ListProperty.class); + private static final Type SET_PROPERTY_TYPE = Type.getType(SetProperty.class); + private static final Type MAP_PROPERTY = Type.getType(MapProperty.class); + private static final Type EXTENSION_CONTAINER_TYPE = Type.getType(ExtensionContainer.class); private static final String RETURN_VOID_FROM_OBJECT = Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE); - private static final String RETURN_VOID_FROM_OBJECT_CLASS_DYNAMIC_OBJECT_INSTANTIATOR = Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE, CLASS_TYPE, DYNAMIC_OBJECT_TYPE, INSTANTIATOR_TYPE); + private static final String RETURN_VOID_FROM_OBJECT_CLASS_DYNAMIC_OBJECT_SERVICE_LOOKUP = Type.getMethodDescriptor(Type.VOID_TYPE, OBJECT_TYPE, CLASS_TYPE, DYNAMIC_OBJECT_TYPE, SERVICE_LOOKUP_TYPE); private static final String RETURN_OBJECT_FROM_STRING_OBJECT_BOOLEAN = Type.getMethodDescriptor(OBJECT_TYPE, OBJECT_TYPE, STRING_TYPE, Type.BOOLEAN_TYPE); private static final String RETURN_CLASS = Type.getMethodDescriptor(CLASS_TYPE); private static final String RETURN_BOOLEAN = Type.getMethodDescriptor(Type.BOOLEAN_TYPE); @@ -310,7 +311,7 @@ private static class ClassBuilderImpl implements ClassGenerationVisitor { private static final String RETURN_CONVENTION = Type.getMethodDescriptor(CONVENTION_TYPE); private static final String RETURN_CONVENTION_MAPPING = Type.getMethodDescriptor(CONVENTION_MAPPING_TYPE); private static final String RETURN_OBJECT = Type.getMethodDescriptor(OBJECT_TYPE); - private static final String RETURN_EXTENSION_CONTAINER = Type.getMethodDescriptor(Type.getType(ExtensionContainer.class)); + private static final String RETURN_EXTENSION_CONTAINER = Type.getMethodDescriptor(EXTENSION_CONTAINER_TYPE); private static final String RETURN_OBJECT_FROM_STRING = Type.getMethodDescriptor(OBJECT_TYPE, STRING_TYPE); private static final String RETURN_OBJECT_FROM_STRING_OBJECT = Type.getMethodDescriptor(OBJECT_TYPE, STRING_TYPE, OBJECT_TYPE); private static final String RETURN_VOID_FROM_STRING_OBJECT = Type.getMethodDescriptor(Type.VOID_TYPE, STRING_TYPE, OBJECT_TYPE); @@ -320,12 +321,17 @@ private static class ClassBuilderImpl implements ClassGenerationVisitor { private static final String RETURN_META_CLASS_REGISTRY = Type.getMethodDescriptor(META_CLASS_REGISTRY_TYPE); private static final String RETURN_SERVICE_REGISTRY = Type.getMethodDescriptor(SERVICE_REGISTRY_TYPE); private static final String RETURN_SERVICE_LOOKUP = Type.getMethodDescriptor(SERVICE_LOOKUP_TYPE); - private static final String RETURN_INSTANTIATOR = Type.getMethodDescriptor(INSTANTIATOR_TYPE); private static final String RETURN_META_CLASS = Type.getMethodDescriptor(META_CLASS_TYPE); private static final String RETURN_VOID_FROM_META_CLASS = Type.getMethodDescriptor(Type.VOID_TYPE, META_CLASS_TYPE); private static final String GET_DECLARED_METHOD_DESCRIPTOR = Type.getMethodDescriptor(METHOD_TYPE, STRING_TYPE, CLASS_ARRAY_TYPE); private static final String RETURN_OBJECT_FROM_TYPE = Type.getMethodDescriptor(OBJECT_TYPE, JAVA_LANG_REFLECT_TYPE); private static final String RETURN_CONFIGURABLE_FILE_COLLECTION = Type.getMethodDescriptor(CONFIGURABLE_FILE_COLLECTION_TYPE); + private static final String RETURN_REGULAR_FILE_PROPERTY = Type.getMethodDescriptor(REGULAR_FILE_PROPERTY_TYPE); + private static final String RETURN_DIRECTORY_PROPERTY = Type.getMethodDescriptor(DIRECTORY_PROPERTY_TYPE); + private static final String RETURN_PROPERTY_FROM_CLASS = Type.getMethodDescriptor(PROPERTY_TYPE, CLASS_TYPE); + private static final String RETURN_LIST_PROPERTY_FROM_CLASS = Type.getMethodDescriptor(LIST_PROPERTY_TYPE, CLASS_TYPE); + private static final String RETURN_SET_PROPERTY_FROM_CLASS = Type.getMethodDescriptor(SET_PROPERTY_TYPE, CLASS_TYPE); + private static final String RETURN_MAP_PROPERTY_FROM_CLASS_CLASS = Type.getMethodDescriptor(MAP_PROPERTY, CLASS_TYPE, CLASS_TYPE); private static final String[] EMPTY_STRINGS = new String[0]; private static final Type[] EMPTY_TYPES = new Type[0]; @@ -337,7 +343,6 @@ private static class ClassBuilderImpl implements ClassGenerationVisitor { private final Type superclassType; private final Map genericReturnTypeConstantsIndex = Maps.newHashMap(); private final AsmClassGenerator classGenerator; - private final boolean requiresInstantiator; private boolean hasMappingField; private final boolean conventionAware; private final boolean mixInDsl; @@ -355,7 +360,6 @@ private ClassBuilderImpl(Class type, boolean decorated, String suffix, boolea mixInDsl = decorated; this.extensible = extensible; this.conventionAware = conventionAware; - requiresInstantiator = !DynamicObjectAware.class.isAssignableFrom(type) && extensible; this.providesOwnDynamicObject = providesOwnDynamicObject; this.requiresServicesMethod = requiresServicesMethod; } @@ -397,6 +401,7 @@ public void startClass() { if (requiresServicesMethod) { generateServiceRegistrySupportMethods(); } + generatePublicTypeMethod(); } @Override @@ -414,6 +419,7 @@ public void addDefaultConstructor() { methodVisitor.visitEnd(); } + @Override public void addConstructor(Constructor constructor) { List paramTypes = new ArrayList(); for (Class paramType : constructor.getParameterTypes()) { @@ -455,19 +461,13 @@ private void initializeFields(MethodVisitor methodVisitor) { methodVisitor.visitMethodInsn(INVOKESTATIC, ASM_BACKED_CLASS_GENERATOR_TYPE.getInternalName(), "getServicesForNext", RETURN_SERVICE_LOOKUP, false); methodVisitor.visitFieldInsn(PUTFIELD, generatedType.getInternalName(), SERVICES_FIELD, SERVICE_LOOKUP_TYPE.getDescriptor()); } - if (requiresInstantiator) { - // this.instantiator = AsmBackedClassGenerator.getInstantiatorForNext() - methodVisitor.visitVarInsn(ALOAD, 0); - methodVisitor.visitMethodInsn(INVOKESTATIC, ASM_BACKED_CLASS_GENERATOR_TYPE.getInternalName(), "getInstantiatorForNext", RETURN_INSTANTIATOR, false); - methodVisitor.visitFieldInsn(PUTFIELD, generatedType.getInternalName(), INSTANTIATOR_FIELD, INSTANTIATOR_TYPE.getDescriptor()); - } } @Override public void addExtensionsProperty() { // GENERATE public ExtensionContainer getExtensions() { return getConvention(); } - addGetter("getExtensions", RETURN_EXTENSION_CONTAINER, null, new MethodCodeBody() { + addGetter("getExtensions", EXTENSION_CONTAINER_TYPE, RETURN_EXTENSION_CONTAINER, null, new MethodCodeBody() { public void add(MethodVisitor visitor) { // GENERATE getConvention() @@ -478,6 +478,7 @@ public void add(MethodVisitor visitor) { }); } + @Override public void mixInDynamicAware() { if (!mixInDsl) { return; @@ -488,16 +489,11 @@ public void mixInDynamicAware() { // END - if (requiresInstantiator) { - // GENERATE private Instantiator instantiator - visitor.visitField(Opcodes.ACC_PRIVATE, INSTANTIATOR_FIELD, INSTANTIATOR_TYPE.getDescriptor(), null, null); - } - if (extensible) { // GENERATE public Convention getConvention() { return getAsDynamicObject().getConvention(); } - addGetter("getConvention", RETURN_CONVENTION, null, new MethodCodeBody() { + addGetter("getConvention", CONVENTION_TYPE, RETURN_CONVENTION, null, new MethodCodeBody() { public void add(MethodVisitor visitor) { // GENERATE ((MixInExtensibleDynamicObject)getAsDynamicObject()).getConvention() @@ -522,7 +518,7 @@ public void add(MethodVisitor visitor) { // return dynamicObjectHelper; // } - addLazyGetter("getAsDynamicObject", RETURN_DYNAMIC_OBJECT, null, DYNAMIC_OBJECT_HELPER_FIELD, ABSTRACT_DYNAMIC_OBJECT_TYPE, new MethodCodeBody() { + addLazyGetter("getAsDynamicObject", DYNAMIC_OBJECT_TYPE, RETURN_DYNAMIC_OBJECT, null, DYNAMIC_OBJECT_HELPER_FIELD, ABSTRACT_DYNAMIC_OBJECT_TYPE, new MethodCodeBody() { public void add(MethodVisitor visitor) { generateCreateDynamicObject(visitor); } @@ -534,7 +530,7 @@ public void add(MethodVisitor visitor) { private void generateCreateDynamicObject(MethodVisitor visitor) { if (extensible) { - // GENERATE new MixInExtensibleDynamicObject(this, getClass().getSuperClass(), super.getAsDynamicObject(), this.instantiator) + // GENERATE new MixInExtensibleDynamicObject(this, getClass().getSuperClass(), super.getAsDynamicObject(), this.services()) visitor.visitTypeInsn(Opcodes.NEW, EXTENSIBLE_DYNAMIC_OBJECT_HELPER_TYPE.getInternalName()); visitor.visitInsn(Opcodes.DUP); @@ -554,11 +550,10 @@ private void generateCreateDynamicObject(MethodVisitor visitor) { visitor.visitInsn(Opcodes.ACONST_NULL); } - // GENERATE this.instantiator - visitor.visitVarInsn(ALOAD, 0); - visitor.visitFieldInsn(GETFIELD, generatedType.getInternalName(), INSTANTIATOR_FIELD, INSTANTIATOR_TYPE.getDescriptor()); + // GENERATE this.services() + putServiceRegistryOnStack(visitor); - visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, EXTENSIBLE_DYNAMIC_OBJECT_HELPER_TYPE.getInternalName(), "", RETURN_VOID_FROM_OBJECT_CLASS_DYNAMIC_OBJECT_INSTANTIATOR, false); + visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, EXTENSIBLE_DYNAMIC_OBJECT_HELPER_TYPE.getInternalName(), "", RETURN_VOID_FROM_OBJECT_CLASS_DYNAMIC_OBJECT_SERVICE_LOOKUP, false); // END } else { @@ -574,6 +569,7 @@ private void generateCreateDynamicObject(MethodVisitor visitor) { } } + @Override public void mixInConventionAware() { // GENERATE private ConventionMapping mapping @@ -610,11 +606,12 @@ public void add(MethodVisitor visitor) { } }; - addLazyGetter("getConventionMapping", RETURN_CONVENTION_MAPPING, null, MAPPING_FIELD, CONVENTION_MAPPING_TYPE, initConventionAwareHelper); + addLazyGetter("getConventionMapping", CONVENTION_MAPPING_TYPE, RETURN_CONVENTION_MAPPING, null, MAPPING_FIELD, CONVENTION_MAPPING_TYPE, initConventionAwareHelper); // END } + @Override public void mixInGroovyObject() { if (!mixInDsl) { return; @@ -645,7 +642,7 @@ public void add(MethodVisitor visitor) { } }; - addLazyGetter("getMetaClass", RETURN_META_CLASS, null, META_CLASS_FIELD, META_CLASS_TYPE, initMetaClass); + addLazyGetter("getMetaClass", META_CLASS_TYPE, RETURN_META_CLASS, null, META_CLASS_FIELD, META_CLASS_TYPE, initMetaClass); // END @@ -670,7 +667,7 @@ private void addSetter(String methodName, String methodDescriptor, MethodCodeBod } @Override - public void addPropertySetters(PropertyMetaData property, Method getter) { + public void addPropertySetters(PropertyMetadata property, Method getter) { if (!mixInDsl) { return; } @@ -695,8 +692,8 @@ public void addPropertySetters(PropertyMetaData property, Method getter) { /** * Adds a getter that returns the value of the given field, initializing it if null using the given code. The code should leave the value on the top of the stack. */ - private void addLazyGetter(String methodName, String methodDescriptor, String signature, final String fieldName, final Type fieldType, final MethodCodeBody initializer) { - addGetter(methodName, methodDescriptor, signature, new MethodCodeBody() { + private void addLazyGetter(String methodName, Type returnType, String methodDescriptor, String signature, final String fieldName, final Type fieldType, final MethodCodeBody initializer) { + addGetter(methodName, returnType, methodDescriptor, signature, new MethodCodeBody() { @Override public void add(MethodVisitor visitor) { // var = this. @@ -722,15 +719,16 @@ public void add(MethodVisitor visitor) { /** * Adds a getter that returns the value that the given code leaves on the top of the stack. */ - private void addGetter(String methodName, String methodDescriptor, String signature, MethodCodeBody body) { + private void addGetter(String methodName, Type returnType, String methodDescriptor, String signature, MethodCodeBody body) { MethodVisitor methodVisitor = visitor.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDescriptor, signature, EMPTY_STRINGS); methodVisitor.visitCode(); body.add(methodVisitor); - methodVisitor.visitInsn(Opcodes.ARETURN); + methodVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); methodVisitor.visitMaxs(0, 0); methodVisitor.visitEnd(); } + @Override public void addDynamicMethods() { if (!mixInDsl) { return; @@ -738,7 +736,7 @@ public void addDynamicMethods() { // GENERATE public Object getProperty(String name) { return getAsDynamicObject().getProperty(name); } - addGetter("getProperty", RETURN_OBJECT_FROM_STRING, null, new MethodCodeBody() { + addGetter("getProperty", OBJECT_TYPE, RETURN_OBJECT_FROM_STRING, null, new MethodCodeBody() { public void add(MethodVisitor methodVisitor) { // GENERATE getAsDynamicObject().getProperty(name); @@ -792,7 +790,7 @@ public void add(MethodVisitor methodVisitor) { // GENERATE public Object invokeMethod(String name, Object params) { return getAsDynamicObject().invokeMethod(name, (Object[])params); } - addGetter("invokeMethod", RETURN_OBJECT_FROM_STRING_OBJECT, null, + addGetter("invokeMethod", OBJECT_TYPE, RETURN_OBJECT_FROM_STRING_OBJECT, null, new MethodCodeBody() { public void add(MethodVisitor methodVisitor) { String invokeMethodDesc = Type.getMethodDescriptor(OBJECT_TYPE, STRING_TYPE, OBJECT_ARRAY_TYPE); @@ -832,7 +830,8 @@ public void add(MethodVisitor methodVisitor) { }); } - public void applyServiceInjectionToProperty(PropertyMetaData property) { + @Override + public void applyServiceInjectionToProperty(PropertyMetadata property) { // GENERATE private ; String fieldName = propFieldName(property); visitor.visitField(Opcodes.ACC_PRIVATE, fieldName, Type.getDescriptor(property.getType()), null, null); @@ -859,12 +858,13 @@ private void generateGetServices() { mv.visitEnd(); } - public void applyServiceInjectionToGetter(PropertyMetaData property, Method getter) { + @Override + public void applyServiceInjectionToGetter(PropertyMetadata property, MethodMetadata getter) { applyServiceInjectionToGetter(property, null, getter); } @Override - public void applyServiceInjectionToGetter(PropertyMetaData property, final Class annotation, Method getter) { + public void applyServiceInjectionToGetter(PropertyMetadata property, final Class annotation, MethodMetadata getter) { // GENERATE public () { if ( == null) { = >.get(>); } return } final String getterName = getter.getName(); Type returnType = Type.getType(getter.getReturnType()); @@ -872,9 +872,9 @@ public void applyServiceInjectionToGetter(PropertyMetaData property, final Class final Type serviceType = Type.getType(property.getType()); final java.lang.reflect.Type genericServiceType = property.getGenericType(); String propFieldName = propFieldName(property); - String signature = signature(getter); + String signature = getterSignature(getter.getGenericReturnType()); - addLazyGetter(getterName, methodDescriptor, signature, propFieldName, serviceType, new MethodCodeBody() { + addLazyGetter(getterName, returnType, methodDescriptor, signature, propFieldName, serviceType, new MethodCodeBody() { @Override public void add(MethodVisitor methodVisitor) { putServiceRegistryOnStack(methodVisitor); @@ -916,7 +916,7 @@ private void putServiceRegistryOnStack(MethodVisitor methodVisitor) { } @Override - public void applyServiceInjectionToSetter(PropertyMetaData property, Class annotation, Method setter) { + public void applyServiceInjectionToSetter(PropertyMetadata property, Class annotation, Method setter) { applyServiceInjectionToSetter(property, setter); } @@ -930,51 +930,85 @@ private String getConstantNameForGenericReturnType(java.lang.reflect.Type generi return entry.fieldName; } - public void applyServiceInjectionToSetter(PropertyMetaData property, Method setter) { + @Override + public void applyServiceInjectionToSetter(PropertyMetadata property, Method setter) { addSetterForProperty(property, setter); } @Override - public void applyManagedStateToProperty(PropertyMetaData property) { + public void applyManagedStateToProperty(PropertyMetadata property) { // GENERATE private ; String fieldName = propFieldName(property); visitor.visitField(Opcodes.ACC_PRIVATE, fieldName, Type.getDescriptor(property.getType()), null, null); } @Override - public void applyReadOnlyManagedStateToGetter(PropertyMetaData property, Method getter) { - // GENERATE public () { if ( == null) { = services.get(ObjectFactory.class).fileCollection(); } return } + public void applyReadOnlyManagedStateToGetter(PropertyMetadata property, Method getter) { + // GENERATE public () { if ( == null) { = services.get(ObjectFactory.class).(xxx); } return } Type propType = Type.getType(property.getType()); - addLazyGetter(getter.getName(), Type.getMethodDescriptor(Type.getType(getter.getReturnType())), null, propFieldName(property), propType, methodVisitor -> { - // GENERATE services.get(ProjectLayout.class) + Type returnType = Type.getType(getter.getReturnType()); + addLazyGetter(getter.getName(), returnType, Type.getMethodDescriptor(returnType), null, propFieldName(property), propType, methodVisitor -> { + // GENERATE services.get(ObjectFactory.class) putServiceRegistryOnStack(methodVisitor); methodVisitor.visitLdcInsn(OBJECT_FACTORY_TYPE); methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, SERVICE_LOOKUP_TYPE.getInternalName(), "get", RETURN_OBJECT_FROM_TYPE, true); methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, OBJECT_FACTORY_TYPE.getInternalName()); - // GENERATE objectFactory.configurableFiles() - methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, OBJECT_FACTORY_TYPE.getInternalName(), "fileCollection", RETURN_CONFIGURABLE_FILE_COLLECTION, true); + if (property.getType().equals(ConfigurableFileCollection.class)) { + // GENERATE objectFactory.fileCollection() + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, OBJECT_FACTORY_TYPE.getInternalName(), "fileCollection", RETURN_CONFIGURABLE_FILE_COLLECTION, true); + } else if (property.getType().equals(RegularFileProperty.class)) { + // GENERATE objectFactory.fileProperty() + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, OBJECT_FACTORY_TYPE.getInternalName(), "fileProperty", RETURN_REGULAR_FILE_PROPERTY, true); + } else if (property.getType().equals(DirectoryProperty.class)) { + // GENERATE objectFactory.directoryProperty() + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, OBJECT_FACTORY_TYPE.getInternalName(), "directoryProperty", RETURN_DIRECTORY_PROPERTY, true); + } else if (property.getType().equals(Property.class)) { + // GENERATE objectFactory.property(type) + Class elementType = (Class) ((ParameterizedType) property.getGenericType()).getActualTypeArguments()[0]; + methodVisitor.visitLdcInsn(Type.getType(elementType)); + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, OBJECT_FACTORY_TYPE.getInternalName(), "property", RETURN_PROPERTY_FROM_CLASS, true); + } else if (property.getType().equals(ListProperty.class)) { + // GENERATE objectFactory.listProperty(type) + Class elementType = (Class) ((ParameterizedType) property.getGenericType()).getActualTypeArguments()[0]; + methodVisitor.visitLdcInsn(Type.getType(elementType)); + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, OBJECT_FACTORY_TYPE.getInternalName(), "listProperty", RETURN_LIST_PROPERTY_FROM_CLASS, true); + } else if (property.getType().equals(SetProperty.class)) { + // GENERATE objectFactory.setProperty(type) + Class elementType = (Class) ((ParameterizedType) property.getGenericType()).getActualTypeArguments()[0]; + methodVisitor.visitLdcInsn(Type.getType(elementType)); + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, OBJECT_FACTORY_TYPE.getInternalName(), "setProperty", RETURN_SET_PROPERTY_FROM_CLASS, true); + } else if (property.getType().equals(MapProperty.class)) { + // GENERATE objectFactory.setProperty(type) + Class keyType = (Class) ((ParameterizedType) property.getGenericType()).getActualTypeArguments()[0]; + Class elementType = (Class) ((ParameterizedType) property.getGenericType()).getActualTypeArguments()[1]; + methodVisitor.visitLdcInsn(Type.getType(keyType)); + methodVisitor.visitLdcInsn(Type.getType(elementType)); + methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, OBJECT_FACTORY_TYPE.getInternalName(), "mapProperty", RETURN_MAP_PROPERTY_FROM_CLASS_CLASS, true); + } else { + throw new IllegalArgumentException(); + } }); } @Override - public void applyManagedStateToGetter(PropertyMetaData property, Method getter) { + public void applyManagedStateToGetter(PropertyMetadata property, Method getter) { // GENERATE public () { return } Type returnType = Type.getType(getter.getReturnType()); String methodDescriptor = Type.getMethodDescriptor(returnType); String fieldName = propFieldName(property); - addGetter(getter.getName(), methodDescriptor, null, methodVisitor -> { + addGetter(getter.getName(), returnType, methodDescriptor, null, methodVisitor -> { methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitFieldInsn(GETFIELD, generatedType.getInternalName(), fieldName, returnType.getDescriptor()); }); } @Override - public void applyManagedStateToSetter(PropertyMetaData property, Method setter) { + public void applyManagedStateToSetter(PropertyMetadata property, Method setter) { addSetterForProperty(property, setter); } - private void addSetterForProperty(PropertyMetaData property, Method setter) { + private void addSetterForProperty(PropertyMetadata property, Method setter) { // GENERATE public void ( value) { == value } String methodDescriptor = Type.getMethodDescriptor(setter); Type fieldType = Type.getType(property.getType()); @@ -985,7 +1019,7 @@ private void addSetterForProperty(PropertyMetaData property, Method setter) { // this.field = value methodVisitor.visitVarInsn(ALOAD, 0); - methodVisitor.visitVarInsn(ALOAD, 1); + methodVisitor.visitVarInsn(fieldType.getOpcode(Opcodes.ILOAD), 1); methodVisitor.visitFieldInsn(PUTFIELD, generatedType.getInternalName(), propFieldName, fieldType.getDescriptor()); // return @@ -994,8 +1028,24 @@ private void addSetterForProperty(PropertyMetaData property, Method setter) { methodVisitor.visitEnd(); } + private void generatePublicTypeMethod() { + // Generate: Class publicType() { ... } + MethodVisitor methodVisitor = visitor.visitMethod(ACC_PUBLIC, "publicType", RETURN_CLASS, null, EMPTY_STRINGS); + methodVisitor.visitLdcInsn(superclassType); + methodVisitor.visitInsn(ARETURN); + methodVisitor.visitMaxs(0, 0); + methodVisitor.visitEnd(); + + // Generate: static Class generatedFrom() { ... } + methodVisitor = visitor.visitMethod(ACC_PUBLIC | ACC_STATIC, "generatedFrom", RETURN_CLASS, null, EMPTY_STRINGS); + methodVisitor.visitLdcInsn(superclassType); + methodVisitor.visitInsn(ARETURN); + methodVisitor.visitMaxs(0, 0); + methodVisitor.visitEnd(); + } + @Override - public void addManagedMethods(List mutableProperties, List readOnlyProperties) { + public void addManagedMethods(List mutableProperties, List readOnlyProperties) { visitor.visitField(PV_FINAL_STATIC, FACTORY_FIELD, Type.getType(Managed.Factory.class).getDescriptor(), null, null); // Generate: (Object[] state) { } @@ -1007,22 +1057,22 @@ public void addManagedMethods(List mutableProperties, List", RETURN_VOID, false); } for (int i = 0; i < mutableProperties.size(); i++) { - PropertyMetaData propertyMetaData = mutableProperties.get(i); + PropertyMetadata propertyMetaData = mutableProperties.get(i); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitVarInsn(ALOAD, 1); methodVisitor.visitLdcInsn(i); methodVisitor.visitInsn(AALOAD); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getType(propertyMetaData.getType()).getInternalName()); + unboxOrCast(methodVisitor, propertyMetaData.getType(), Type.getType(propertyMetaData.getType())); String propFieldName = propFieldName(propertyMetaData); methodVisitor.visitFieldInsn(PUTFIELD, generatedType.getInternalName(), propFieldName, Type.getType(propertyMetaData.getType()).getDescriptor()); } for (int i = 0; i < readOnlyProperties.size(); i++) { - PropertyMetaData propertyMetaData = readOnlyProperties.get(i); + PropertyMetadata propertyMetaData = readOnlyProperties.get(i); methodVisitor.visitVarInsn(ALOAD, 0); methodVisitor.visitVarInsn(ALOAD, 1); methodVisitor.visitLdcInsn(i + mutableProperties.size()); methodVisitor.visitInsn(AALOAD); - methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, Type.getType(propertyMetaData.getType()).getInternalName()); + unboxOrCast(methodVisitor, propertyMetaData.getType(), Type.getType(propertyMetaData.getType())); String propFieldName = propFieldName(propertyMetaData); methodVisitor.visitFieldInsn(PUTFIELD, generatedType.getInternalName(), propFieldName, Type.getType(propertyMetaData.getType()).getDescriptor()); } @@ -1030,13 +1080,6 @@ public void addManagedMethods(List mutableProperties, List && } methodVisitor = visitor.visitMethod(ACC_PUBLIC, "immutable", RETURN_BOOLEAN, null, EMPTY_STRINGS); // Could return true if all of the read only properties point to immutable objects, but at this stage there are no such types supported @@ -1051,21 +1094,24 @@ public void addManagedMethods(List mutableProperties, List mutableProperties, List targetClass, Type targetType) { + if (targetClass.isPrimitive()) { // Unbox value + Type boxedType = Type.getType(JavaReflectionUtil.getWrapperTypeForPrimitiveType(targetClass)); methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, boxedType.getInternalName()); - String valueMethodDescriptor = Type.getMethodDescriptor(returnType); - methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, boxedType.getInternalName(), getter.getReturnType().getName() + "Value", valueMethodDescriptor, false); + String valueMethodDescriptor = Type.getMethodDescriptor(targetType); + methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, boxedType.getInternalName(), targetClass.getName() + "Value", valueMethodDescriptor, false); } else { // Cast to return type methodVisitor.visitTypeInsn(Opcodes.CHECKCAST, - getter.getReturnType().isArray() ? "[" + returnType.getElementType().getDescriptor() - : returnType.getInternalName()); + targetClass.isArray() ? "[" + targetType.getElementType().getDescriptor() + : targetType.getInternalName()); } + } - methodVisitor.visitInsn(returnType.getOpcode(IRETURN)); - methodVisitor.visitMaxs(0, 0); - methodVisitor.visitEnd(); + /** + * Boxes the value at the top of the stack, if primitive + */ + private void maybeBox(MethodVisitor methodVisitor, Class valueClass, Type valueType) { + if (valueClass.isPrimitive()) { + // Box value + Type boxedType = Type.getType(JavaReflectionUtil.getWrapperTypeForPrimitiveType(valueClass)); + String valueOfMethodDescriptor = Type.getMethodDescriptor(boxedType, valueType); + methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, boxedType.getInternalName(), "valueOf", valueOfMethodDescriptor, false); + } } - public void applyConventionMappingToSetter(PropertyMetaData property, Method setter) { + @Override + public void applyConventionMappingToSetter(PropertyMetadata property, Method setter) { if (!conventionAware) { return; } @@ -1207,7 +1270,8 @@ public void applyConventionMappingToSetter(PropertyMetaData property, Method set methodVisitor.visitEnd(); } - public void addSetMethod(PropertyMetaData property, Method setter) { + @Override + public void addSetMethod(PropertyMetadata property, Method setter) { if (!mixInDsl) { return; } @@ -1235,7 +1299,8 @@ public void addSetMethod(PropertyMetaData property, Method setter) { methodVisitor.visitEnd(); } - public void applyConventionMappingToSetMethod(PropertyMetaData property, Method method) { + @Override + public void applyConventionMappingToSetMethod(PropertyMetadata property, Method method) { if (!mixInDsl || !conventionAware) { return; } @@ -1268,6 +1333,7 @@ public void applyConventionMappingToSetMethod(PropertyMetaData property, Method methodVisitor.visitEnd(); } + @Override public void addActionMethod(Method method) { if (!mixInDsl) { return; @@ -1311,7 +1377,7 @@ public Type transform(Class clazz) { methodVisitor.visitEnd(); } - void generateServiceRegistrySupportMethods() { + private void generateServiceRegistrySupportMethods() { generateServicesField(); generateGetServices(); } @@ -1388,6 +1454,7 @@ private Object getAnnotationParameterValue(Annotation annotation, Method method) } } + @Override public Class generate() { writeGenericReturnTypeFields(); visitor.visitEnd(); @@ -1487,63 +1554,63 @@ public void addExtensionsProperty() { } @Override - public void applyServiceInjectionToProperty(PropertyMetaData property) { + public void applyServiceInjectionToProperty(PropertyMetadata property) { } @Override - public void applyServiceInjectionToGetter(PropertyMetaData property, Method getter) { + public void applyServiceInjectionToGetter(PropertyMetadata property, MethodMetadata getter) { } @Override - public void applyServiceInjectionToSetter(PropertyMetaData property, Method setter) { + public void applyServiceInjectionToSetter(PropertyMetadata property, Method setter) { } @Override - public void applyServiceInjectionToGetter(PropertyMetaData property, Class annotation, Method getter) { + public void applyServiceInjectionToGetter(PropertyMetadata property, Class annotation, MethodMetadata getter) { } @Override - public void applyServiceInjectionToSetter(PropertyMetaData property, Class annotation, Method setter) { + public void applyServiceInjectionToSetter(PropertyMetadata property, Class annotation, Method setter) { } @Override - public void applyManagedStateToProperty(PropertyMetaData property) { + public void applyManagedStateToProperty(PropertyMetadata property) { } @Override - public void applyReadOnlyManagedStateToGetter(PropertyMetaData property, Method getter) { + public void applyReadOnlyManagedStateToGetter(PropertyMetadata property, Method getter) { } @Override - public void applyManagedStateToGetter(PropertyMetaData property, Method getter) { + public void applyManagedStateToGetter(PropertyMetadata property, Method getter) { } @Override - public void applyManagedStateToSetter(PropertyMetaData property, Method setter) { + public void applyManagedStateToSetter(PropertyMetadata property, Method setter) { } @Override - public void addManagedMethods(List properties, List readOnlyProperties) { + public void addManagedMethods(List properties, List readOnlyProperties) { } @Override - public void applyConventionMappingToProperty(PropertyMetaData property) { + public void applyConventionMappingToProperty(PropertyMetadata property) { } @Override - public void applyConventionMappingToGetter(PropertyMetaData property, Method getter) { + public void applyConventionMappingToGetter(PropertyMetadata property, Method getter) { } @Override - public void applyConventionMappingToSetter(PropertyMetaData property, Method setter) { + public void applyConventionMappingToSetter(PropertyMetadata property, Method setter) { } @Override - public void applyConventionMappingToSetMethod(PropertyMetaData property, Method metaMethod) { + public void applyConventionMappingToSetMethod(PropertyMetadata property, Method metaMethod) { } @Override - public void addSetMethod(PropertyMetaData propertyMetaData, Method setter) { + public void addSetMethod(PropertyMetadata propertyMetaData, Method setter) { } @Override @@ -1551,7 +1618,7 @@ public void addActionMethod(Method method) { } @Override - public void addPropertySetters(PropertyMetaData property, Method getter) { + public void addPropertySetters(PropertyMetadata property, Method getter) { } @Override diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInstantiationScheme.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInstantiationScheme.java index 68fba5ab02fc7..92bee7eda6986 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInstantiationScheme.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInstantiationScheme.java @@ -20,15 +20,25 @@ import org.gradle.internal.service.ServiceLookup; import org.gradle.internal.service.ServiceRegistry; +import java.lang.annotation.Annotation; +import java.util.Set; + class DefaultInstantiationScheme implements InstantiationScheme { private final DependencyInjectingInstantiator instantiator; private final ConstructorSelector constructorSelector; + private final Set> injectionAnnotations; - public DefaultInstantiationScheme(ConstructorSelector constructorSelector, ServiceRegistry defaultServices) { + public DefaultInstantiationScheme(ConstructorSelector constructorSelector, ServiceRegistry defaultServices, Set> injectionAnnotations) { this.constructorSelector = constructorSelector; + this.injectionAnnotations = injectionAnnotations; this.instantiator = new DependencyInjectingInstantiator(constructorSelector, defaultServices); } + @Override + public Set> getInjectionAnnotations() { + return injectionAnnotations; + } + @Override public InstanceFactory forType(Class type) { return instantiator.factoryFor(type); diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInstantiatorFactory.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInstantiatorFactory.java index 5378b1f269cd5..4b9776ad40592 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInstantiatorFactory.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/DefaultInstantiatorFactory.java @@ -16,25 +16,20 @@ package org.gradle.internal.instantiation; -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableSet; -import com.google.common.util.concurrent.UncheckedExecutionException; import org.gradle.cache.internal.CrossBuildInMemoryCacheFactory; -import org.gradle.internal.UncheckedException; import org.gradle.internal.reflect.Instantiator; import org.gradle.internal.service.DefaultServiceRegistry; import org.gradle.internal.service.ServiceLookup; import org.gradle.internal.service.ServiceRegistry; +import javax.inject.Inject; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; -import java.util.Set; public class DefaultInstantiatorFactory implements InstantiatorFactory { - private final ServiceRegistry noServices = new DefaultServiceRegistry(); + private final ServiceRegistry defaultServices; private final CrossBuildInMemoryCacheFactory cacheFactory; private final List annotationHandlers; private final DefaultInstantiationScheme injectOnlyScheme; @@ -42,33 +37,22 @@ public class DefaultInstantiatorFactory implements InstantiatorFactory { private final DefaultInstantiationScheme decoratingScheme; private final DefaultInstantiationScheme decoratingLenientScheme; - // Assume for now that the annotations are all part of Gradle core and are never unloaded, so use strong references to the annotation types - private final LoadingCache>, InstantiationScheme> schemes = CacheBuilder.newBuilder().build(new CacheLoader>, InstantiationScheme>() { - @Override - public InstantiationScheme load(Set> annotations) { - for (Class annotation : annotations) { - assertKnownAnnotation(annotation); - } - ClassGenerator classGenerator = AsmBackedClassGenerator.injectOnly(annotationHandlers, annotations); - Jsr330ConstructorSelector constructorSelector = new Jsr330ConstructorSelector(classGenerator, cacheFactory.newClassCache()); - return new DefaultInstantiationScheme(constructorSelector, noServices); - } - }); - public DefaultInstantiatorFactory(CrossBuildInMemoryCacheFactory cacheFactory, List annotationHandlers) { this.cacheFactory = cacheFactory; this.annotationHandlers = annotationHandlers; + DefaultServiceRegistry services = new DefaultServiceRegistry(); + services.add(InstantiatorFactory.class, this); + this.defaultServices = services; ClassGenerator injectOnly = AsmBackedClassGenerator.injectOnly(annotationHandlers, ImmutableSet.of()); ClassGenerator decorated = AsmBackedClassGenerator.decorateAndInject(annotationHandlers, ImmutableSet.of()); ConstructorSelector injectOnlyJsr330Selector = new Jsr330ConstructorSelector(injectOnly, cacheFactory.newClassCache()); ConstructorSelector decoratedJsr330Selector = new Jsr330ConstructorSelector(decorated, cacheFactory.newClassCache()); ConstructorSelector injectOnlyLenientSelector = new ParamsMatchingConstructorSelector(injectOnly, cacheFactory.newClassCache()); ConstructorSelector decoratedLenientSelector = new ParamsMatchingConstructorSelector(decorated, cacheFactory.newClassCache()); - injectOnlyScheme = new DefaultInstantiationScheme(injectOnlyJsr330Selector, noServices); - injectOnlyLenientScheme = new DefaultInstantiationScheme(injectOnlyLenientSelector, noServices); - decoratingScheme = new DefaultInstantiationScheme(decoratedJsr330Selector, noServices); - decoratingLenientScheme = new DefaultInstantiationScheme(decoratedLenientSelector, noServices); - schemes.put(ImmutableSet.of(), injectOnlyScheme); + injectOnlyScheme = new DefaultInstantiationScheme(injectOnlyJsr330Selector, defaultServices, ImmutableSet.of(Inject.class)); + injectOnlyLenientScheme = new DefaultInstantiationScheme(injectOnlyLenientSelector, defaultServices, ImmutableSet.of(Inject.class)); + decoratingScheme = new DefaultInstantiationScheme(decoratedJsr330Selector, defaultServices, ImmutableSet.of(Inject.class)); + decoratingLenientScheme = new DefaultInstantiationScheme(decoratedLenientSelector, defaultServices, ImmutableSet.of(Inject.class)); } @Override @@ -88,11 +72,19 @@ public InstantiationScheme injectScheme() { @Override public InstantiationScheme injectScheme(Collection> injectAnnotations) { - try { - return schemes.getUnchecked(ImmutableSet.copyOf(injectAnnotations)); - } catch (UncheckedExecutionException e) { - throw UncheckedException.throwAsUncheckedException(e.getCause()); + if (injectAnnotations.isEmpty()) { + return injectOnlyScheme; + } + + for (Class annotation : injectAnnotations) { + assertKnownAnnotation(annotation); } + ClassGenerator classGenerator = AsmBackedClassGenerator.injectOnly(annotationHandlers, ImmutableSet.copyOf(injectAnnotations)); + Jsr330ConstructorSelector constructorSelector = new Jsr330ConstructorSelector(classGenerator, cacheFactory.newClassCache()); + ImmutableSet.Builder> builder = ImmutableSet.builderWithExpectedSize(injectAnnotations.size() + 1); + builder.addAll(injectAnnotations); + builder.add(Inject.class); + return new DefaultInstantiationScheme(constructorSelector, defaultServices, builder.build()); } @Override @@ -127,7 +119,7 @@ public Instantiator injectAndDecorate(ServiceLookup services) { private void assertKnownAnnotation(Class annotation) { for (InjectAnnotationHandler annotationHandler : annotationHandlers) { - if (annotationHandler.getAnnotation().equals(annotation)) { + if (annotationHandler.getAnnotationType().equals(annotation)) { return; } } diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InjectAnnotationHandler.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InjectAnnotationHandler.java index 98dd663af76c8..78b57916343b1 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InjectAnnotationHandler.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InjectAnnotationHandler.java @@ -24,5 +24,5 @@ *

    Implementations must be registered as global scoped services.

    */ public interface InjectAnnotationHandler { - Class getAnnotation(); + Class getAnnotationType(); } diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InstantiationScheme.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InstantiationScheme.java index 50afe8be77769..66af72caba077 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InstantiationScheme.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InstantiationScheme.java @@ -19,12 +19,20 @@ import org.gradle.internal.reflect.Instantiator; import org.gradle.internal.service.ServiceLookup; +import java.lang.annotation.Annotation; +import java.util.Set; + /** * A scheme, or strategy, for creating objects. * *

    Implementations are provided by a {@link InstantiatorFactory}.

    */ public interface InstantiationScheme { + /** + * Returns the set of annotations that are used by this instantiation scheme for dependency injection. + */ + Set> getInjectionAnnotations(); + /** * Creates a new {@link InstanceFactory} for the given type, which creates instances based on the configuration of this scheme. */ diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InstantiatorFactory.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InstantiatorFactory.java index 6ef5642fcbc49..6765775e78b13 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InstantiatorFactory.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/InstantiatorFactory.java @@ -52,7 +52,7 @@ public interface InstantiatorFactory { InstantiationScheme injectScheme(); /** - * Create an {@link InstantiationScheme} that can inject services and user provided values into the instances it creates, but does not decorate the instances. Supports using the {@link javax.inject.Inject} annotation plus the given custom inject annotations. Is not lenient. + * Create a new {@link InstantiationScheme} that can inject services and user provided values into the instances it creates, but does not decorate the instances. Supports using the {@link javax.inject.Inject} annotation plus the given custom inject annotations. Is not lenient. * * @param injectAnnotations Zero or more annotations that mark properties whose value will be injected on creation. Each annotation must be known to this factory via a {@link InjectAnnotationHandler}. */ diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/ManagedTypeFactory.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/ManagedTypeFactory.java index eb7880afe75bb..2ee0789dbc753 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/ManagedTypeFactory.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/ManagedTypeFactory.java @@ -17,6 +17,7 @@ package org.gradle.internal.instantiation; import org.gradle.api.reflect.ObjectInstantiationException; +import org.gradle.internal.state.Managed; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -31,6 +32,9 @@ public ManagedTypeFactory(Class type) throws NoSuchMethodException { @Override public T fromState(Class type, Object state) { + if (!type.isAssignableFrom(constructor.getDeclaringClass())) { + return null; + } try { return type.cast(constructor.newInstance(state)); } catch (InvocationTargetException e) { diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/MixInExtensibleDynamicObject.java b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/MixInExtensibleDynamicObject.java index 8d9fd79dbcccb..77985c7a6b107 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/MixInExtensibleDynamicObject.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/MixInExtensibleDynamicObject.java @@ -21,6 +21,7 @@ import org.gradle.internal.metaobject.BeanDynamicObject; import org.gradle.internal.metaobject.DynamicObject; import org.gradle.internal.reflect.Instantiator; +import org.gradle.internal.service.ServiceLookup; import javax.annotation.Nullable; @@ -29,8 +30,13 @@ */ public class MixInExtensibleDynamicObject extends ExtensibleDynamicObject { // Used by generated code - public MixInExtensibleDynamicObject(Object decoratedObject, Class publicType, @Nullable DynamicObject selfProvidedDynamicObject, Instantiator instantiator) { - super(decoratedObject, wrap(decoratedObject, publicType, selfProvidedDynamicObject), instantiator); + public MixInExtensibleDynamicObject(Object decoratedObject, Class publicType, @Nullable DynamicObject selfProvidedDynamicObject, ServiceLookup services) { + super(decoratedObject, wrap(decoratedObject, publicType, selfProvidedDynamicObject), instantiator(services)); + } + + private static Instantiator instantiator(ServiceLookup services) { + InstantiatorFactory instantiatorFactory = (InstantiatorFactory) services.get(InstantiatorFactory.class); + return instantiatorFactory.injectAndDecorateLenient(services); } private static AbstractDynamicObject wrap(Object delegateObject, Class publicType, DynamicObject dynamicObject) { diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/reflect/JavaPropertyReflectionUtil.java b/subprojects/model-core/src/main/java/org/gradle/internal/reflect/JavaPropertyReflectionUtil.java index 68b9806bccbd9..d89edccc20948 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/reflect/JavaPropertyReflectionUtil.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/reflect/JavaPropertyReflectionUtil.java @@ -16,14 +16,24 @@ package org.gradle.internal.reflect; +import com.google.common.reflect.TypeToken; import org.apache.commons.lang.reflect.MethodUtils; import org.gradle.internal.UncheckedException; +import org.gradle.util.CollectionUtils; import javax.annotation.Nullable; import java.lang.annotation.Annotation; import java.lang.annotation.Inherited; +import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Queue; import java.util.Set; import java.util.WeakHashMap; @@ -152,6 +162,116 @@ public static boolean hasDefaultToString(Object object) { } } + /** + * Resolves the return type of a method in a given class. + * + * For example, for {@code MyList implements List}, resolving the return type of {@link List#get(int)} in {@code MyList} yields {@link String}. + */ + public static Type resolveMethodReturnType(Class type, Method method) { + Type returnType = method.getGenericReturnType(); + if (type.equals(method.getDeclaringClass())) { + // No need to resolve type parameters if the method is from the same class. + return returnType; + } + // Checking if there is a type variable to resolve, since resolving the type variable via `TypeToken` is quite expensive. + return hasTypeVariable(returnType) ? TypeToken.of(type).method(method).getReturnType().getType() : returnType; + } + + /** + * Checks if a type has a type variable which may require resolving. + */ + public static boolean hasTypeVariable(Type type) { + // do some checks up-front, so we avoid creating the queue in most cases + // Cases we want to handle: + // - List + // - Class + // - List> + // - Integer[] + // - ? extends BaseType + // - Class[] + if (doesNotHaveTypeVariable(type)) { + return false; + } + if (type instanceof TypeVariable) { + return true; + } + if (type instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) type; + boolean noTypeVariables = true; + for (Type actualTypeArgument : parameterizedType.getActualTypeArguments()) { + if (actualTypeArgument instanceof TypeVariable) { + return true; + } + noTypeVariables &= doesNotHaveTypeVariable(actualTypeArgument); + } + if (noTypeVariables) { + return false; + } + } + if (type instanceof GenericArrayType) { + GenericArrayType genericArrayType = (GenericArrayType) type; + if (genericArrayType.getGenericComponentType() instanceof TypeVariable) { + return true; + } + } + + // Type is more complicated, need to check everything. + Queue typesToInspect = new ArrayDeque(); + typesToInspect.add(type); + while (!typesToInspect.isEmpty()) { + Type typeToInspect = typesToInspect.remove(); + if (typeToInspect instanceof Class) { + continue; + } + if (typeToInspect instanceof TypeVariable) { + return true; + } + if (typeToInspect instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) typeToInspect; + CollectionUtils.addAll(typesToInspect, parameterizedType.getActualTypeArguments()); + } else if (typeToInspect instanceof GenericArrayType) { + GenericArrayType arrayType = (GenericArrayType) typeToInspect; + typesToInspect.add(arrayType.getGenericComponentType()); + } else if (typeToInspect instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) typeToInspect; + CollectionUtils.addAll(typesToInspect, wildcardType.getLowerBounds()); + CollectionUtils.addAll(typesToInspect, wildcardType.getUpperBounds()); + } else { + // We don't know what the type is - let Guava take care of it. + return true; + } + } + return false; + } + + /** + * Quick check if a type does not have any type variables. + * + * Handled cases: + * - raw Class + * - Wildcard type with Class bounds, e.g. ? extends BaseType + */ + private static boolean doesNotHaveTypeVariable(Type type) { + if (type instanceof Class) { + return true; + } + if (type instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) type; + for (Type lowerBound : wildcardType.getLowerBounds()) { + if (!(lowerBound instanceof Class)) { + return false; + } + } + for (Type upperBound : wildcardType.getUpperBounds()) { + if (!(upperBound instanceof Class)) { + return false; + } + } + return true; + } + return false; + } + private static class GetterMethodBackedPropertyAccessor implements PropertyAccessor { private final String property; private final Method method; diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/reflect/ParameterValidationContext.java b/subprojects/model-core/src/main/java/org/gradle/internal/reflect/ParameterValidationContext.java index 89a779bafd00b..5459d1b04a65b 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/reflect/ParameterValidationContext.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/reflect/ParameterValidationContext.java @@ -21,15 +21,30 @@ public interface ParameterValidationContext { ParameterValidationContext NOOP = new ParameterValidationContext() { @Override - public void recordValidationMessage(@Nullable String ownerPath, String propertyName, String message) { + public void visitError(@Nullable String ownerPath, String propertyName, String message) { } @Override - public void recordValidationMessage(String message) { + public void visitError(String message) { + } + + @Override + public void visitErrorStrict(String message) { } }; - void recordValidationMessage(@Nullable String ownerPath, String propertyName, String message); + /** + * Visits a validation error associated with the given property. + */ + void visitError(@Nullable String ownerPath, String propertyName, String message); + + /** + * Visits a validation error. + */ + void visitError(String message); - void recordValidationMessage(String message); + /** + * Visits a strict validation error. Strict errors are not ignored for tasks, whereas for backwards compatibility other errors are ignored (at runtime) or treated as warnings (at plugin build time). + */ + void visitErrorStrict(String message); } diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/reflect/PropertyExtractor.java b/subprojects/model-core/src/main/java/org/gradle/internal/reflect/PropertyExtractor.java index bd86b726ccac7..0d12423efe727 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/reflect/PropertyExtractor.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/reflect/PropertyExtractor.java @@ -20,7 +20,6 @@ import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; -import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; @@ -28,7 +27,6 @@ import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Sets; -import org.gradle.internal.Pair; import org.gradle.util.CollectionUtils; import javax.annotation.Nonnull; @@ -77,7 +75,7 @@ private static ImmutableSet> allMethodsOf(Iterable Pair, ImmutableList> extractPropertyMetadata(Class type) { + public ImmutableSet extractPropertyMetadata(Class type, ParameterValidationContext validationContext) { final Set> propertyTypeAnnotations = primaryAnnotationTypes; final Map properties = Maps.newLinkedHashMap(); Types.walkTypeHierarchy(type, ignoredSuperclasses, new Types.TypeVisitor() { @@ -105,46 +103,44 @@ public void visitType(Class type) { properties.put(fieldName, propertyMetadata); } - Iterable declaredAnnotations = mergeDeclaredAnnotations(method, field, propertyMetadata); + Iterable declaredAnnotations = mergeDeclaredAnnotations(method, field, propertyMetadata, validationContext); // Discard overridden property type annotations when an overriding annotation is also present Iterable overriddenAnnotations = filterOverridingAnnotations(declaredAnnotations, propertyTypeAnnotations); - recordAnnotations(propertyMetadata, overriddenAnnotations, declaredAnnotations, propertyTypeAnnotations); + recordAnnotations(propertyMetadata, overriddenAnnotations, declaredAnnotations, propertyTypeAnnotations, validationContext); } } }); ImmutableSet.Builder propertyBuilder = ImmutableSet.builderWithExpectedSize(properties.size()); - ImmutableList.Builder problemBuilder = ImmutableList.builder(); for (PropertyMetadataBuilder property : properties.values()) { - validateProperty(property); - problemBuilder.addAll(property.validationProblems); + validateProperty(property, validationContext); if (property.propertyType != null) { propertyBuilder.add(property.toMetadata()); } } - return Pair.of(propertyBuilder.build(), problemBuilder.build()); + return propertyBuilder.build(); } - private void validateProperty(PropertyMetadataBuilder property) { + private void validateProperty(PropertyMetadataBuilder property, ParameterValidationContext validationContext) { if (!property.brokenType && property.propertyType == null) { - property.validationMessage("is not annotated with " + displayName); + validationContext.visitError(null, property.fieldName, "is not annotated with " + displayName); } } - private Iterable mergeDeclaredAnnotations(Method method, @Nullable Field field, PropertyMetadataBuilder property) { - Collection methodAnnotations = collectRelevantAnnotations(method.getDeclaredAnnotations(), property); + private Iterable mergeDeclaredAnnotations(Method method, @Nullable Field field, PropertyMetadataBuilder property, ParameterValidationContext validationContext) { + Collection methodAnnotations = collectRelevantAnnotations(method.getDeclaredAnnotations(), property, validationContext); if (Modifier.isPrivate(method.getModifiers())) { if (!methodAnnotations.isEmpty()) { - property.validationMessage("is private and annotated with @" + methodAnnotations.iterator().next().annotationType().getSimpleName()); + validationContext.visitError(null, property.fieldName, "is private and annotated with @" + methodAnnotations.iterator().next().annotationType().getSimpleName()); } property.hasBrokenType(); } if (field == null) { return methodAnnotations; } - Collection fieldAnnotations = collectRelevantAnnotations(field.getDeclaredAnnotations(), property); + Collection fieldAnnotations = collectRelevantAnnotations(field.getDeclaredAnnotations(), property, validationContext); if (fieldAnnotations.isEmpty()) { return methodAnnotations; } @@ -157,7 +153,7 @@ private Iterable mergeDeclaredAnnotations(Method method, @Nullable F while (iFieldAnnotation.hasNext()) { Annotation fieldAnnotation = iFieldAnnotation.next(); if (methodAnnotation.annotationType().equals(fieldAnnotation.annotationType())) { - property.validationMessage("has both a getter and field declared with annotation @" + methodAnnotation.annotationType().getSimpleName()); + validationContext.visitError(null, property.fieldName, "has both a getter and field declared with annotation @" + methodAnnotation.annotationType().getSimpleName()); iFieldAnnotation.remove(); } } @@ -186,7 +182,7 @@ public boolean apply(Annotation input) { }); } - private void recordAnnotations(PropertyMetadataBuilder property, Iterable overriddenAnnotations, Iterable annotations, Set> propertyTypeAnnotations) { + private void recordAnnotations(PropertyMetadataBuilder property, Iterable overriddenAnnotations, Iterable annotations, Set> propertyTypeAnnotations, ParameterValidationContext validationContext) { Set> declaredPropertyTypes = Sets.newLinkedHashSet(); for (Annotation annotation : overriddenAnnotations) { if (propertyTypeAnnotations.contains(annotation.annotationType())) { @@ -207,18 +203,18 @@ public String apply(Class annotationType) { } }); Set sortedNames = CollectionUtils.addAll(new TreeSet(), names); - property.validationMessage("has conflicting property types declared: " + Joiner.on(", ").join(sortedNames)); + validationContext.visitError(null, property.fieldName, "has conflicting property types declared: " + Joiner.on(", ").join(sortedNames)); } } - private Collection collectRelevantAnnotations(Annotation[] annotations, PropertyMetadataBuilder property) { + private Collection collectRelevantAnnotations(Annotation[] annotations, PropertyMetadataBuilder property, ParameterValidationContext validationContext) { List relevantAnnotations = Lists.newArrayListWithCapacity(annotations.length); for (Annotation annotation : annotations) { if (relevantAnnotationTypes.contains(annotation.annotationType())) { relevantAnnotations.add(annotation); } if (otherKnownAnnotations.contains(annotation.annotationType())) { - property.validationMessage("is annotated with unsupported annotation @" + annotation.annotationType().getSimpleName()); + validationContext.visitError(null, property.fieldName, "is annotated with unsupported annotation @" + annotation.annotationType().getSimpleName()); property.hasBrokenType(); } } @@ -282,7 +278,6 @@ private static class PropertyMetadataBuilder { private final Method method; private Class propertyType; private final Map, Annotation> annotations = Maps.newHashMap(); - private final List validationProblems = Lists.newArrayList(); private boolean brokenType; PropertyMetadataBuilder(Set> propertyTypeAnnotations, String fieldName, Method method) { @@ -295,15 +290,6 @@ public void hasBrokenType() { this.brokenType = true; } - public void validationMessage(String message) { - validationProblems.add(new ValidationProblem() { - @Override - public void collect(@Nullable String ownerPropertyPath, ParameterValidationContext validationContext) { - validationContext.recordValidationMessage(ownerPropertyPath, fieldName, message); - } - }); - } - public void addAnnotation(Annotation annotation) { Class annotationType = annotation.annotationType(); // Record the most specific property type annotation only diff --git a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/Managed.java b/subprojects/model-core/src/main/java/org/gradle/internal/state/Managed.java similarity index 64% rename from subprojects/model-core/src/main/java/org/gradle/internal/instantiation/Managed.java rename to subprojects/model-core/src/main/java/org/gradle/internal/state/Managed.java index dfafd0e9ac06a..20357989c3efa 100644 --- a/subprojects/model-core/src/main/java/org/gradle/internal/instantiation/Managed.java +++ b/subprojects/model-core/src/main/java/org/gradle/internal/state/Managed.java @@ -14,15 +14,19 @@ * limitations under the License. */ -package org.gradle.internal.instantiation; +package org.gradle.internal.state; + +import javax.annotation.Nullable; /** - * Mixed into each generated class, to mark it as fully managed. + * Implemented by types whose state is fully managed by Gradle. Mixed into generated classes whose state is fully managed. */ public interface Managed { /** * Returns a snapshot of the current state of this object. This can be passed to the {@link Factory#fromState(Class, Object)} method to recreate this object from the snapshot. * Note that the state may not be immutable, so should be made isolated to reuse in another context. The state can also be fingerprinted to generate a fingerprint of this object. + * + *

    Note that currently the state should reference only JVM and core Gradle types when {@link #immutable()} returns true.

    */ Object unpackState(); @@ -31,11 +35,23 @@ public interface Managed { */ boolean immutable(); + /** + * Returns the public type of this managed instance. Currently is used to identify the implementation. + */ Class publicType(); + /** + * Returns the factory that can be used to create new instances of this type. + */ Factory managedFactory(); interface Factory { + /** + * Creates an instance of the given type from the given state, if possible. + * + * @return null when the given type is not supported. + */ + @Nullable T fromState(Class type, Object state); } } diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/asm/AsmClassGeneratorUtils.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/asm/AsmClassGeneratorUtils.java index 74e4c25bab1e6..d5c2e0a63c6dc 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/asm/AsmClassGeneratorUtils.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/asm/AsmClassGeneratorUtils.java @@ -18,7 +18,12 @@ import org.objectweb.asm.Type; -import java.lang.reflect.*; +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; public class AsmClassGeneratorUtils { @@ -34,6 +39,13 @@ public static String signature(Constructor constructor) { return builder.toString(); } + public static String getterSignature(java.lang.reflect.Type returnType) { + StringBuilder builder = new StringBuilder(); + builder.append("()"); + visitType(returnType, builder); + return builder.toString(); + } + /** * Generates the signature for the given method */ diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelRegistration.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelRegistration.java index 330fff71efc11..f28fea16d2519 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelRegistration.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelRegistration.java @@ -18,7 +18,7 @@ import com.google.common.base.Preconditions; import com.google.common.collect.Multimap; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor; @ThreadSafe diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelViewState.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelViewState.java index 198dd06f76314..337ea300b0471 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelViewState.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/DefaultModelViewState.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.core; -import net.jcip.annotations.NotThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; import org.gradle.api.Action; import org.gradle.model.ModelViewClosedException; import org.gradle.model.ReadOnlyModelViewException; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceModelView.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceModelView.java index 7884fa021a300..3eada97de0829 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceModelView.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/InstanceModelView.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.core; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Action; import org.gradle.internal.Actions; import org.gradle.model.internal.type.ModelType; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPath.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPath.java index 468d3a6aed094..f24a4da523954 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPath.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelPath.java @@ -21,7 +21,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Lists; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.GradleException; import org.gradle.internal.exceptions.Contextual; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelReference.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelReference.java index ede8585c2d760..ea1593a3eaa03 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelReference.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelReference.java @@ -18,7 +18,7 @@ import com.google.common.base.Objects; import com.google.common.base.Preconditions; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.Cast; import org.gradle.model.internal.type.ModelType; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelRegistrations.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelRegistrations.java index 17c40811e23a9..4000fefd9d2f1 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelRegistrations.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/ModelRegistrations.java @@ -20,8 +20,8 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimap; -import net.jcip.annotations.NotThreadSafe; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Action; import org.gradle.api.Transformer; import org.gradle.internal.Actions; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypeCompatibilityModelProjectionSupport.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypeCompatibilityModelProjectionSupport.java index c3493ff7d3b97..d906e47ded1be 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypeCompatibilityModelProjectionSupport.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/TypeCompatibilityModelProjectionSupport.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.core; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.Cast; import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor; import org.gradle.model.internal.type.ModelType; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/UnmanagedModelProjection.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/UnmanagedModelProjection.java index 46100d87b645a..af3faa11683b3 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/UnmanagedModelProjection.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/UnmanagedModelProjection.java @@ -17,7 +17,7 @@ package org.gradle.model.internal.core; import com.google.common.base.Optional; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.Cast; import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor; import org.gradle.model.internal.type.ModelType; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/AbstractModelRuleDescriptor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/AbstractModelRuleDescriptor.java index 813bb855627b3..4fe61e736c4bd 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/AbstractModelRuleDescriptor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/AbstractModelRuleDescriptor.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.core.rule.describe; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.internal.cache.StringInterner; @ThreadSafe diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/MethodModelRuleDescriptor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/MethodModelRuleDescriptor.java index 887ae12c995f8..8b24bbd54bb73 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/MethodModelRuleDescriptor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/MethodModelRuleDescriptor.java @@ -20,7 +20,7 @@ import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.collect.Iterables; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.UncheckedIOException; import org.gradle.model.internal.method.WeaklyTypeReferencingMethod; import org.gradle.model.internal.type.ModelType; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/NestedModelRuleDescriptor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/NestedModelRuleDescriptor.java index fe014c04901c3..01a15d2fc9a84 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/NestedModelRuleDescriptor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/NestedModelRuleDescriptor.java @@ -17,7 +17,7 @@ package org.gradle.model.internal.core.rule.describe; import com.google.common.base.Objects; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.UncheckedIOException; import java.io.IOException; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/SimpleModelRuleDescriptor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/SimpleModelRuleDescriptor.java index bba5d0322e9ca..07973e6937e27 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/SimpleModelRuleDescriptor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/core/rule/describe/SimpleModelRuleDescriptor.java @@ -17,7 +17,7 @@ package org.gradle.model.internal.core.rule.describe; import com.google.common.base.Objects; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.UncheckedIOException; import org.gradle.internal.Factories; import org.gradle.internal.Factory; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultMethodRuleDefinition.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultMethodRuleDefinition.java index e9a921c243f9c..c3155cae838d4 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultMethodRuleDefinition.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultMethodRuleDefinition.java @@ -17,7 +17,7 @@ package org.gradle.model.internal.inspect; import com.google.common.collect.ImmutableList; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.specs.Spec; import org.gradle.internal.Cast; import org.gradle.model.Path; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultsModelRuleExtractor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultsModelRuleExtractor.java index 631092203f34b..1728ea6f1b4f7 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultsModelRuleExtractor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/DefaultsModelRuleExtractor.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.inspect; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.Defaults; import org.gradle.model.internal.core.ModelActionRole; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/FinalizeModelRuleExtractor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/FinalizeModelRuleExtractor.java index 4657021211151..93aeca82dfa4a 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/FinalizeModelRuleExtractor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/FinalizeModelRuleExtractor.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.inspect; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.Finalize; import org.gradle.model.internal.core.ModelActionRole; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MethodModelRuleExtractors.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MethodModelRuleExtractors.java index ed6bf1d92260d..85989ccb6d10f 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MethodModelRuleExtractors.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MethodModelRuleExtractors.java @@ -17,7 +17,7 @@ package org.gradle.model.internal.inspect; import com.google.common.collect.ImmutableList; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.internal.manage.schema.ModelSchemaStore; import java.util.List; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ModelRuleExtractor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ModelRuleExtractor.java index 0c88fd3801dee..c98ee033d3369 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ModelRuleExtractor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ModelRuleExtractor.java @@ -23,7 +23,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import com.google.common.util.concurrent.UncheckedExecutionException; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Transformer; import org.gradle.internal.Cast; import org.gradle.internal.Factory; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ModelRuleSourceDetector.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ModelRuleSourceDetector.java index 12eb516c8cccb..c77ec764e7e19 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ModelRuleSourceDetector.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ModelRuleSourceDetector.java @@ -25,7 +25,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.Cast; import org.gradle.internal.UncheckedException; import org.gradle.model.RuleSource; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MutateModelRuleExtractor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MutateModelRuleExtractor.java index a6bd6dc30b944..86bafb310bf48 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MutateModelRuleExtractor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/MutateModelRuleExtractor.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.inspect; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.Mutate; import org.gradle.model.internal.core.ModelActionRole; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ValidateModelRuleExtractor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ValidateModelRuleExtractor.java index c91b8c95592f3..031bd1331f39d 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ValidateModelRuleExtractor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/inspect/ValidateModelRuleExtractor.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.inspect; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.Validate; import org.gradle.model.internal.core.ModelActionRole; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/AbstractModelSchema.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/AbstractModelSchema.java index f853f1157b5f4..22e4a712c7d8d 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/AbstractModelSchema.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/AbstractModelSchema.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.manage.schema; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.internal.type.ModelType; @ThreadSafe diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelProperty.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelProperty.java index bea59ad81b1f1..46aa3e2d1a69c 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelProperty.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelProperty.java @@ -20,7 +20,7 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.Cast; import org.gradle.internal.reflect.PropertyAccessorType; import org.gradle.model.internal.method.WeaklyTypeReferencingMethod; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchemaStore.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchemaStore.java index 275d4f5f8c145..3baa61a357c97 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchemaStore.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/ModelSchemaStore.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.manage.schema; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.internal.type.ModelType; @ThreadSafe diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStore.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStore.java index 46455b9829922..81de1e1f9c2dc 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStore.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/manage/schema/extract/DefaultModelSchemaStore.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.manage.schema.extract; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.internal.manage.schema.ModelSchema; import org.gradle.model.internal.manage.schema.ModelSchemaStore; import org.gradle.model.internal.manage.schema.cache.ModelSchemaCache; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/DefaultModelRegistry.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/DefaultModelRegistry.java index 636986e9cbf90..a7463e39051b7 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/DefaultModelRegistry.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/DefaultModelRegistry.java @@ -21,7 +21,7 @@ import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; -import net.jcip.annotations.NotThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; import org.gradle.model.ConfigurationCycleException; import org.gradle.model.InvalidModelRuleDeclarationException; import org.gradle.model.RuleSource; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelPathSuggestionProvider.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelPathSuggestionProvider.java index 5318db41493b3..e3b3ca6b2b92c 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelPathSuggestionProvider.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/ModelPathSuggestionProvider.java @@ -19,7 +19,7 @@ import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.apache.commons.lang.StringUtils; import org.gradle.api.Transformer; import org.gradle.model.internal.core.ModelPath; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBinder.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBinder.java index 23adf071e2ec5..becb29b5ef6aa 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBinder.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/RuleBinder.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.registry; -import net.jcip.annotations.NotThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; import org.gradle.api.Action; import org.gradle.model.internal.core.ModelAction; import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/UnboundRulesProcessor.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/UnboundRulesProcessor.java index 8bb3386f5c0f0..12ddeb7d7e1df 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/UnboundRulesProcessor.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/registry/UnboundRulesProcessor.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.registry; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Transformer; import org.gradle.model.internal.core.ModelNode; import org.gradle.model.internal.core.ModelPath; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/AmbiguousBindingReporter.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/AmbiguousBindingReporter.java index f905274040a3c..fef4950f41be3 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/AmbiguousBindingReporter.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/AmbiguousBindingReporter.java @@ -17,7 +17,7 @@ package org.gradle.model.internal.report; import com.google.common.collect.ImmutableList; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.internal.core.ModelPath; import org.gradle.model.internal.core.ModelReference; import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/IncompatibleTypeReferenceReporter.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/IncompatibleTypeReferenceReporter.java index d29f266a2beb7..88c8e40427fac 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/IncompatibleTypeReferenceReporter.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/IncompatibleTypeReferenceReporter.java @@ -16,7 +16,7 @@ package org.gradle.model.internal.report; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.internal.core.ModelPath; import org.gradle.model.internal.core.MutableModelNode; import org.gradle.model.internal.core.rule.describe.ModelRuleDescriptor; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRule.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRule.java index d76fe5fbccb0e..73798c488dca3 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRule.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRule.java @@ -17,8 +17,8 @@ package org.gradle.model.internal.report.unbound; import com.google.common.collect.ImmutableList; -import net.jcip.annotations.NotThreadSafe; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import java.io.File; import java.util.List; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRuleInput.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRuleInput.java index b6c2e5664c610..95934e50085c3 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRuleInput.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRuleInput.java @@ -17,8 +17,8 @@ package org.gradle.model.internal.report.unbound; import com.google.common.collect.ImmutableList; -import net.jcip.annotations.NotThreadSafe; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.model.internal.core.ModelPath; import org.gradle.model.internal.type.ModelType; diff --git a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRulesReporter.java b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRulesReporter.java index ec362da93cc79..9d826bfad37f1 100644 --- a/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRulesReporter.java +++ b/subprojects/model-core/src/main/java/org/gradle/model/internal/report/unbound/UnboundRulesReporter.java @@ -17,7 +17,7 @@ package org.gradle.model.internal.report.unbound; import com.google.common.base.Joiner; -import net.jcip.annotations.NotThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; import java.io.PrintWriter; diff --git a/subprojects/model-core/src/test/groovy/org/gradle/api/internal/model/NamedObjectInstantiatorTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/api/internal/model/NamedObjectInstantiatorTest.groovy index 82467e445f8a6..0f088b36d4934 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/api/internal/model/NamedObjectInstantiatorTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/api/internal/model/NamedObjectInstantiatorTest.groovy @@ -18,7 +18,7 @@ package org.gradle.api.internal.model import org.gradle.api.Named import org.gradle.api.reflect.ObjectInstantiationException -import org.gradle.internal.instantiation.Managed +import org.gradle.internal.state.Managed import org.gradle.test.fixtures.concurrent.ConcurrentSpec import org.gradle.util.Matchers import spock.lang.Ignore diff --git a/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/PropertySpec.groovy b/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/PropertySpec.groovy index 09bf45379f00d..4f56b81a073cb 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/PropertySpec.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/PropertySpec.groovy @@ -18,6 +18,7 @@ package org.gradle.api.internal.provider import org.gradle.api.Transformer import org.gradle.api.provider.Provider +import org.gradle.internal.state.Managed import java.util.concurrent.Callable @@ -823,5 +824,31 @@ abstract class PropertySpec extends ProviderSpec { property.get() == someValue() } + def "can unpack state and recreate instance"() { + given: + def property = propertyWithNoValue() + + expect: + property instanceof Managed + !property.immutable() + def state = property.unpackState() + def copy = property.managedFactory().fromState(property.publicType(), state) + !copy.is(property) + !copy.present + copy.getOrNull() == null + + property.set(someValue()) + copy.getOrNull() == null + + def state2 = property.unpackState() + def copy2 = property.managedFactory().fromState(property.publicType(), state2) + !copy2.is(property) + copy2.get() == someValue() + + property.set(someOtherValue()) + copy.getOrNull() == null + copy2.get() == someValue() + } + static class Thing {} } diff --git a/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/ProviderSpec.groovy b/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/ProviderSpec.groovy index 70c4a4281ac16..187a77732e29b 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/ProviderSpec.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/ProviderSpec.groovy @@ -18,6 +18,7 @@ package org.gradle.api.internal.provider import org.gradle.api.Transformer import org.gradle.api.provider.Provider +import org.gradle.internal.state.Managed import spock.lang.Specification abstract class ProviderSpec extends Specification { @@ -29,6 +30,10 @@ abstract class ProviderSpec extends Specification { abstract T someOtherValue() + boolean isNoValueProviderImmutable() { + return false + } + def "can query value when it has as value"() { given: def provider = providerWithValue(someValue()) @@ -204,4 +209,31 @@ abstract class ProviderSpec extends Specification { t.message == "No value has been specified for this provider." } + def "can unpack state and recreate instance when provider has no value"() { + given: + def provider = providerWithNoValue() + + expect: + provider instanceof Managed + provider.immutable() == noValueProviderImmutable + def state = provider.unpackState() + def copy = provider.managedFactory().fromState(provider.publicType(), state) + !copy.is(provider) || noValueProviderImmutable + !copy.present + copy.getOrNull() == null + } + + def "can unpack state and recreate instance when provider has value"() { + given: + def provider = providerWithValue(someValue()) + + expect: + provider instanceof Managed + !provider.immutable() + def state = provider.unpackState() + def copy = provider.managedFactory().fromState(provider.publicType(), state) + !copy.is(provider) + copy.present + copy.getOrNull() == someValue() + } } diff --git a/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/ProvidersTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/ProvidersTest.groovy index 6a963e7d0901b..370991aa7d18c 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/ProvidersTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/api/internal/provider/ProvidersTest.groovy @@ -40,6 +40,11 @@ class ProvidersTest extends ProviderSpec { return 123 } + @Override + boolean isNoValueProviderImmutable() { + return true + } + def "mapped fixed value provider calculates transformed value lazily and caches the result"() { given: def transform = Mock(Transformer) diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AbstractClassGeneratorSpec.groovy b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AbstractClassGeneratorSpec.groovy index c66bd893d5218..f35627c4169b6 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AbstractClassGeneratorSpec.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AbstractClassGeneratorSpec.groovy @@ -16,16 +16,17 @@ package org.gradle.internal.instantiation + import org.gradle.internal.reflect.Instantiator -import org.gradle.internal.service.DefaultServiceRegistry import org.gradle.internal.service.ServiceLookup +import org.gradle.util.TestUtil import spock.lang.Specification abstract class AbstractClassGeneratorSpec extends Specification { abstract ClassGenerator getGenerator() protected T create(Class clazz, Object... args) { - return create(clazz, new DefaultServiceRegistry(), args) + return create(clazz, defaultServices(), args) } protected T create(Class clazz, ServiceLookup services, Object... args) { @@ -33,7 +34,13 @@ abstract class AbstractClassGeneratorSpec extends Specification { } protected T create(ClassGenerator generator, Class clazz, Object... args) { - return create(generator, clazz, new DefaultServiceRegistry(), args) + return create(generator, clazz, defaultServices(), args) + } + + ServiceLookup defaultServices() { + ServiceLookup services = Mock(ServiceLookup) + _ * services.get(InstantiatorFactory.class) >> { TestUtil.instantiatorFactory() } + return services } protected T create(ClassGenerator generator, Class clazz, ServiceLookup services, Object... args) { diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratedManagedStateTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratedManagedStateTest.groovy new file mode 100644 index 0000000000000..3a44cd5a0f09e --- /dev/null +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratedManagedStateTest.groovy @@ -0,0 +1,253 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.instantiation + +import org.gradle.internal.state.Managed +import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider +import org.gradle.util.TestUtil +import org.junit.ClassRule +import spock.lang.Shared +import spock.lang.Unroll + +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.* +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.AbstractBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.AbstractBeanWithInheritedFields +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.Bean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.BeanWithAbstractProperty +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfaceBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfaceDirectoryPropertyBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfaceFileCollectionBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfaceFilePropertyBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfaceListPropertyBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfaceMapPropertyBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfacePropertyBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfaceSetPropertyBean +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InterfaceWithDefaultMethods + +class AsmBackedClassGeneratedManagedStateTest extends AbstractClassGeneratorSpec { + @ClassRule + @Shared + TestNameTestDirectoryProvider tmpDir = new TestNameTestDirectoryProvider() + final ClassGenerator generator = AsmBackedClassGenerator.injectOnly([], []) + + def canConstructInstanceOfAbstractClassWithAbstractPropertyGetterAndSetter() { + def bean = create(BeanWithAbstractProperty) + + expect: + bean.name == null + bean.setName("name") + bean.name == "name" + } + + def canUnpackAndRecreateAbstractClassWithAbstractPropertyGetterAndSetter() { + def bean = create(BeanWithAbstractProperty) + + expect: + bean instanceof Managed + bean.publicType() == BeanWithAbstractProperty + !bean.immutable() + def state = bean.unpackState() + state.length == 1 + state[0] == null + + def copy = bean.managedFactory().fromState(BeanWithAbstractProperty, state) + !copy.is(bean) + copy.name == null + + bean.setName("name") + copy.name == null + + def state2 = bean.unpackState() + state2.length == 1 + state2[0] == "name" + + def copy2 = bean.managedFactory().fromState(BeanWithAbstractProperty, state2) + !copy2.is(bean) + copy2.name == "name" + } + + def canConstructInstanceOfInterfaceWithPropertyGetterAndSetter() { + def bean = create(InterfaceBean) + + expect: + bean.name == null + bean.setName("name") + bean.name == "name" + + bean.numbers == null + bean.setNumbers([12] as Set) + bean.numbers == [12] as Set + } + + def canUnpackAndRecreateInstanceOfInterface() throws Exception { + def bean = create(InterfaceBean.class) + + expect: + bean instanceof Managed + bean.publicType() == InterfaceBean + !bean.immutable() + def state = bean.unpackState() + state.length == 2 + state[0] == null + state[1] == null + + def copy = bean.managedFactory().fromState(InterfaceBean, state) + !copy.is(bean) + copy.name == null + copy.numbers == null + + bean.setName("name") + bean.setNumbers([12] as Set) + copy.name == null + copy.numbers == null + + def state2 = bean.unpackState() + state2.length == 2 + state2[0] == "name" + state2[1] == [12] as Set + + def copy2 = bean.managedFactory().fromState(InterfaceBean, state2) + !copy2.is(bean) + copy2.name == "name" + copy2.numbers == [12] as Set + } + + def canConstructInstanceOfInterfaceWithPrimitivePropertyGetterAndSetter() { + def bean = create(InterfacePrimitiveBean) + + expect: + !bean.prop1 + bean.setProp1(true) + bean.prop1 + + bean.prop2 == 0 + bean.setProp2(12) + bean.prop2 == 12 + } + + def canConstructInstanceOfInterfaceWithFileCollectionGetter() { + def projectDir = tmpDir.testDirectory + def bean = create(InterfaceFileCollectionBean, TestUtil.createRootProject(projectDir).services) + + expect: + bean.prop.empty + bean.prop.from("a", "b") + bean.prop.files == [projectDir.file("a"), projectDir.file("b")] as Set + } + + @Unroll + def "canConstructInstanceOfInterfaceWithGetterOfFilePropertyType #type.simpleName"() { + def projectDir = tmpDir.testDirectory + def bean = create(type, TestUtil.createRootProject(projectDir).services) + + expect: + bean.prop.getOrNull() == null + bean.prop.set(projectDir.file("a")) + bean.prop.get().asFile == projectDir.file("a") + + where: + type << [InterfaceFilePropertyBean, InterfaceDirectoryPropertyBean] + } + + @Unroll + def "canConstructInstanceOfInterfaceWithGetterOfMutableType #type.simpleName"() { + def projectDir = tmpDir.testDirectory + def bean = create(type, TestUtil.createRootProject(projectDir).services) + + expect: + bean.prop.getOrNull() == defaultValue + bean.prop.set(newValue) + bean.prop.get() == newValue + + where: + type | defaultValue | newValue + InterfacePropertyBean | null | "value" + InterfaceListPropertyBean | [] | ["a", "b"] + InterfaceSetPropertyBean | [] as Set | ["a", "b"] as Set + InterfaceMapPropertyBean | [:] | [a: 1, b: 12] + } + + @Unroll + def "canUnpackAndRecreateInterfaceWithGetterOfMutableType #type.simpleName"() { + def projectDir = tmpDir.testDirectory + def bean = create(type, TestUtil.createRootProject(projectDir).services) + + expect: + bean instanceof Managed + bean.publicType() == type + !bean.immutable() + def state = bean.unpackState() + state.length == 1 + state[0].is(bean.prop) + + def copy = bean.managedFactory().fromState(type, state) + copy.prop.is(bean.prop) + + where: + type | _ + InterfaceFileCollectionBean | _ + InterfacePropertyBean | _ + InterfaceFilePropertyBean | _ + InterfaceDirectoryPropertyBean | _ + InterfaceListPropertyBean | _ + InterfaceSetPropertyBean | _ + InterfaceMapPropertyBean | _ + } + + def canConstructInstanceOfInterfaceWithDefaultMethodsOnly() { + def bean = create(InterfaceWithDefaultMethods) + + expect: + bean.name == "name" + } + + def canUnpackAndRecreateInstanceOfInterfaceWithDefaultMethodsOnly() { + def bean = create(InterfaceWithDefaultMethods) + + expect: + bean instanceof Managed + bean.publicType() == InterfaceWithDefaultMethods + bean.immutable() + def state = bean.unpackState() + state.length == 0 + + def copy = bean.managedFactory().fromState(InterfaceWithDefaultMethods, state) + !copy.is(bean) + copy.name == "name" + } + + def doesNotMixManagedIntoClassWithFields() { + def bean = create(Bean) + + expect: + !(bean instanceof Managed) + } + + def doesNotMixManagedIntoAbstractClassWithFields() { + def bean = create(AbstractBean, "value") + + expect: + !(bean instanceof Managed) + } + + def doesNotMixManagedIntoClassWithInheritedFields() { + def bean = create(AbstractBeanWithInheritedFields, "value") + + expect: + !(bean instanceof Managed) + } +} diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorDecoratedTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorDecoratedTest.groovy index 03a0a83e4253c..32e8e001462ad 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorDecoratedTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorDecoratedTest.groovy @@ -27,9 +27,22 @@ import org.gradle.internal.util.BiFunction import org.gradle.util.ConfigureUtil import spock.lang.Issue +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.* + class AsmBackedClassGeneratorDecoratedTest extends AbstractClassGeneratorSpec { final ClassGenerator generator = AsmBackedClassGenerator.decorateAndInject([], []) + def "can attach nested extensions to object"() { + given: + def bean = create(Bean) + def e1 = bean.extensions.create('one', Bean) + def e2 = e1.extensions.create('two', Bean) + + expect: + bean.one.is(e1) + bean.one.two.is(e2) + } + @Issue("GRADLE-2417") def "can use dynamic object as closure delegate"() { given: diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorInjectDecoratedTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorInjectDecoratedTest.groovy index 3cc9b18de1805..70b5a49a7ed21 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorInjectDecoratedTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorInjectDecoratedTest.groovy @@ -19,8 +19,8 @@ package org.gradle.internal.instantiation import org.gradle.api.plugins.ExtensionAware import org.gradle.api.plugins.ExtensionContainer import org.gradle.internal.service.DefaultServiceRegistry -import org.gradle.internal.service.ServiceLookup import org.gradle.internal.service.ServiceRegistry +import org.gradle.util.TestUtil import javax.inject.Inject import java.lang.annotation.Annotation @@ -29,8 +29,10 @@ import java.lang.annotation.RetentionPolicy import java.lang.reflect.ParameterizedType import java.lang.reflect.Type +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.AbstractClassRealizingTwoTypeParameters +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.AbstractClassWithConcreteTypeParameter +import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.AbstractClassWithParameterizedTypeParameter import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.FinalInjectBean -import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.InjectPropertyBean import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.NonGetterInjectBean import static org.gradle.internal.instantiation.AsmBackedClassGeneratorTest.PrivateInjectBean @@ -39,7 +41,7 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS def "can inject service using @Inject on a getter method with dummy method body"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() _ * services.get(Number) >> 12 when: @@ -53,7 +55,7 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS def "can inject service using @Inject on an abstract service getter method"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() _ * services.get(Number) >> 12 when: @@ -65,9 +67,87 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS obj.getProperty("thing") == 12 } + def "can inject service using @Inject on a super interface with class type parameter"() { + given: + def services = defaultServices() + _ * services.get(Number) >> 12 + + when: + def obj = create(AbstractClassWithConcreteTypeParameter, services) + + then: + obj.thing == 12 + obj.getThing() == 12 + obj.getProperty("thing") == 12 + obj.doSomething() + + def returnType = obj.getClass().getDeclaredMethod("getThing").genericReturnType + returnType == Number + } + + def "can inject services using @Inject on a super interface with type parameter remapping"() { + given: + def services = defaultServices() + _ * services.get(_) >> { Type type -> + if (type instanceof ParameterizedType) { + assert type.rawType == List.class + assert type.actualTypeArguments.length == 1 + assert type.actualTypeArguments[0] == String + return ["Hello", "Number"] + } + assert type == Number + return 12 + } + + when: + def obj = create(AbstractClassRealizingTwoTypeParameters, services) + + then: + obj.thing == 12 + obj.getThing() == 12 + obj.getProperty("thing") == 12 + obj.getOtherThing() == ["Hello", "Number"] + obj.doSomething() == "Hello Number 12" + + def returnType = obj.getClass().getDeclaredMethod("getThing").genericReturnType + returnType == Number.class + def otherReturnType = obj.getClass().getDeclaredMethod("getOtherThing").genericReturnType + otherReturnType instanceof ParameterizedType + otherReturnType.rawType == List + otherReturnType.actualTypeArguments.length == 1 + otherReturnType.actualTypeArguments[0] == String + } + + def "can inject service using @Inject on a super interface with parameterized type parameters"() { + given: + def services = defaultServices() + _ * services.get(_) >> { Type type -> + assert type instanceof ParameterizedType + assert type.rawType == List.class + assert type.actualTypeArguments.length == 1 + assert type.actualTypeArguments[0] == Number + return [12] + } + + when: + def obj = create(AbstractClassWithParameterizedTypeParameter, services) + + then: + obj.thing == [12] + obj.getThing() == [12] + obj.getProperty("thing") == [12] + obj.doSomething() == "[12]" + + def returnType = obj.getClass().getDeclaredMethod("getThing").genericReturnType + returnType instanceof ParameterizedType + returnType.rawType == List + returnType.actualTypeArguments.length == 1 + returnType.actualTypeArguments[0] == Number + } + def "can inject service using @Inject on an interface getter method"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() _ * services.get(Number) >> 12 when: @@ -81,7 +161,7 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS def "can optionally set injected service using a service setter method"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() when: def obj = create(BeanWithMutableServices, services) @@ -91,15 +171,11 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS obj.thing == 12 obj.getThing() == 12 obj.getProperty("thing") == 12 - - and: - 1 * services.find(InstantiatorFactory) >> null - 0 * services._ } def "retains declared generic type of service getter"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() _ * services.get(_) >> { Type type -> assert type instanceof ParameterizedType assert type.rawType == List.class @@ -125,13 +201,12 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS def "service lookup is lazy and the result is cached"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() when: def obj = create(BeanWithServices, services) then: - 1 * services.find(InstantiatorFactory) >> null 0 * services._ when: @@ -139,7 +214,7 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS then: 1 * services.get(Number) >> 12 - 0 * services._ + 0 * services.get(Number) when: obj.thing @@ -150,7 +225,7 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS def "can inject service using a custom annotation on getter method with dummy method body"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() _ * services.get(Number, CustomInject) >> 12 def generator = AsmBackedClassGenerator.decorateAndInject([new CustomAnnotationHandler()], [CustomInject]) @@ -166,7 +241,7 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS def "can inject service using a custom annotation on abstract getter method"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() _ * services.get(Number, CustomInject) >> 12 def generator = AsmBackedClassGenerator.decorateAndInject([new CustomAnnotationHandler()], [CustomInject]) @@ -193,7 +268,7 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS def "object can provide its own service registry to provide services for injection"() { given: - def services = Mock(ServiceLookup) + def services = defaultServices() when: def obj = create(BeanWithServicesAndServiceRegistry, services) @@ -213,15 +288,6 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS e.cause.message == "Cannot use @Inject annotation on method ExtensibleBeanWithInject.getExtensions()." } - def "cannot attach @Inject annotation to property whose type is Property"() { - when: - generator.generate(InjectPropertyBean) - - then: - def e = thrown(ClassGenerationException) - e.cause.message == "Cannot use @Inject annotation on method InjectPropertyBean.getProp()." - } - def "cannot attach @Inject annotation to final method"() { when: generator.generate(FinalInjectBean) @@ -306,7 +372,7 @@ class AsmBackedClassGeneratorInjectDecoratedTest extends AbstractClassGeneratorS class CustomAnnotationHandler implements InjectAnnotationHandler { @Override - Class getAnnotation() { + Class getAnnotationType() { return CustomInject } } @@ -336,9 +402,10 @@ class BeanWithMutableServices extends BeanWithServices { class BeanWithServicesAndServiceRegistry extends BeanWithServices { ServiceRegistry getServices() { - def services = new DefaultServiceRegistry() - services.add(Number, 12) - return services + def registry = new DefaultServiceRegistry() + registry.add(InstantiatorFactory.class, TestUtil.instantiatorFactory()) + registry.add(Number, 12) + return registry } } diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorTest.java b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorTest.java index d6c0638ec8ef8..f4d7eada84d3f 100755 --- a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorTest.java +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/AsmBackedClassGeneratorTest.java @@ -15,15 +15,19 @@ */ package org.gradle.internal.instantiation; +import com.google.common.base.Joiner; import groovy.lang.Closure; import groovy.lang.GroovyObject; import groovy.lang.MissingMethodException; import org.gradle.api.Action; import org.gradle.api.NonExtensible; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.DirectoryProperty; import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFileProperty; import org.gradle.api.internal.ConventionMapping; import org.gradle.api.internal.DynamicObjectAware; +import org.gradle.api.internal.GeneratedSubclass; import org.gradle.api.internal.GeneratedSubclasses; import org.gradle.api.internal.HasConvention; import org.gradle.api.internal.IConventionAware; @@ -33,7 +37,10 @@ import org.gradle.api.plugins.Convention; import org.gradle.api.plugins.ExtensionAware; import org.gradle.api.plugins.ExtensionContainer; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.MapProperty; import org.gradle.api.provider.Property; +import org.gradle.api.provider.SetProperty; import org.gradle.api.reflect.HasPublicType; import org.gradle.api.reflect.TypeOf; import org.gradle.internal.extensibility.ConventionAwareHelper; @@ -45,7 +52,6 @@ import org.gradle.internal.reflect.JavaReflectionUtil; import org.gradle.internal.service.DefaultServiceRegistry; import org.gradle.internal.service.ServiceRegistry; -import org.gradle.test.fixtures.file.TestFile; import org.gradle.test.fixtures.file.TestNameTestDirectoryProvider; import org.gradle.util.TestUtil; import org.junit.Rule; @@ -79,9 +85,7 @@ import static org.gradle.util.TestUtil.call; import static org.gradle.util.WrapUtil.toList; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.sameInstance; @@ -100,7 +104,9 @@ public class AsmBackedClassGeneratorTest { private final ClassGenerator generator = AsmBackedClassGenerator.decorateAndInject(Collections.emptyList(), Collections.>emptyList()); private T newInstance(Class clazz, Object... args) throws Exception { - return newInstance(clazz, new DefaultServiceRegistry(), args); + DefaultServiceRegistry services = new DefaultServiceRegistry(); + services.add(InstantiatorFactory.class, TestUtil.instantiatorFactory()); + return newInstance(clazz, services, args); } private T newInstance(Class clazz, ServiceRegistry services, Object... args) throws Exception { @@ -127,10 +133,21 @@ private T newInstance(Class clazz, ServiceRegistry services, Object... ar } @Test - public void mixesInGeneratedSubclassInterface() { - Class generatedClass = generator.generate(Bean.class).getGeneratedClass(); - assertEquals(GeneratedSubclasses.unpack(generatedClass), Bean.class); - assertEquals(Bean.class, GeneratedSubclasses.unpack(generatedClass)); + public void mixesInGeneratedSubclassInterface() throws Exception { + Bean bean = newInstance(Bean.class); + assertTrue(bean instanceof GeneratedSubclass); + assertEquals(Bean.class, ((GeneratedSubclass)bean).publicType()); + assertEquals(Bean.class, GeneratedSubclasses.unpackType(bean)); + assertEquals(Bean.class, GeneratedSubclasses.unpack(bean.getClass())); + } + + @Test + public void mixesInGeneratedSubclassInterfaceToInterface() throws Exception { + InterfaceWithDefaultMethods bean = newInstance(InterfaceWithDefaultMethods.class); + assertTrue(bean instanceof GeneratedSubclass); + assertEquals(InterfaceWithDefaultMethods.class, ((GeneratedSubclass)bean).publicType()); + assertEquals(InterfaceWithDefaultMethods.class, GeneratedSubclasses.unpackType(bean)); + assertEquals(InterfaceWithDefaultMethods.class, GeneratedSubclasses.unpack(bean.getClass())); } @Test @@ -448,154 +465,6 @@ public void cannotCreateInstanceOfClassWithAbstractSetMethod() throws Exception } } - @Test - public void canConstructInstanceOfAbstractClassWithAbstractPropertyGetterAndSetter() throws Exception { - BeanWithAbstractProperty bean = newInstance(BeanWithAbstractProperty.class); - - assertThat(bean.getName(), nullValue()); - bean.setName("name"); - assertThat(bean.getName(), equalTo("name")); - } - - @Test - public void canUnpackAndRecreateAbstractClassWithAbstractPropertyGetterAndSetter() throws Exception { - BeanWithAbstractProperty bean = newInstance(BeanWithAbstractProperty.class); - assertThat(bean, instanceOf(Managed.class)); - - Managed managed = (Managed) bean; - assertEquals(BeanWithAbstractProperty.class, managed.publicType()); - assertFalse(managed.immutable()); - Object[] state = (Object[]) managed.unpackState(); - assertThat(state.length, equalTo(1)); - assertThat(state[0], equalTo(null)); - - BeanWithAbstractProperty copy = managed.managedFactory().fromState(BeanWithAbstractProperty.class, state); - assertThat(copy, not(sameInstance(bean))); - assertThat(copy.getName(), nullValue()); - - bean.setName("name"); - - state = (Object[]) managed.unpackState(); - assertThat(state.length, equalTo(1)); - assertThat(state[0], equalTo("name")); - - copy = managed.managedFactory().fromState(BeanWithAbstractProperty.class, state); - assertThat(copy, not(sameInstance(bean))); - assertThat(copy.getName(), equalTo("name")); - } - - @Test - public void canConstructInstanceOfInterfaceWithPropertyGetterAndSetter() throws Exception { - InterfaceBean bean = newInstance(InterfaceBean.class); - - assertThat(bean.getName(), nullValue()); - bean.setName("name"); - assertThat(bean.getName(), equalTo("name")); - - assertThat(bean.getNumbers(), nullValue()); - bean.setNumbers(Collections.singleton(12)); - assertThat(bean.getNumbers(), equalTo(Collections.singleton(12))); - } - - @Test - public void canUnpackAndRecreateInstanceOfInterface() throws Exception { - InterfaceBean bean = newInstance(InterfaceBean.class); - assertThat(bean, instanceOf(Managed.class)); - - Managed managed = (Managed) bean; - assertEquals(InterfaceBean.class, managed.publicType()); - assertFalse(managed.immutable()); - Object[] state = (Object[]) managed.unpackState(); - assertThat(state.length, equalTo(2)); - assertThat(state[0], equalTo(null)); - assertThat(state[1], equalTo(null)); - - InterfaceBean copy = managed.managedFactory().fromState(InterfaceBean.class, state); - assertThat(copy.getName(), nullValue()); - assertThat(copy.getNumbers(), nullValue()); - - bean.setName("name"); - bean.setNumbers(Collections.singleton(12)); - - state = (Object[]) managed.unpackState(); - assertThat(state.length, equalTo(2)); - assertThat(state[0], equalTo("name")); - assertThat(state[1], equalTo(Collections.singleton(12))); - - copy = managed.managedFactory().fromState(InterfaceBean.class, state); - assertThat(copy.getName(), equalTo("name")); - assertThat(copy.getNumbers(), equalTo(Collections.singleton(12))); - } - - @Test - public void canConstructInstanceOfInterfaceWithFileCollectionGetter() throws Exception { - TestFile projectDir = tmpDir.getTestDirectory(); - InterfaceFileCollectionBean bean = newInstance(InterfaceFileCollectionBean.class, TestUtil.createRootProject(projectDir).getServices()); - - assertTrue(bean.getFiles().isEmpty()); - - bean.getFiles().from("a", "b"); - - assertThat(bean.getFiles(), hasItems(projectDir.file("a"), projectDir.file("b"))); - } - - @Test - public void canUnpackAndRecreateInterfaceWithFileCollectionGetter() throws Exception { - TestFile projectDir = tmpDir.getTestDirectory(); - InterfaceFileCollectionBean bean = newInstance(InterfaceFileCollectionBean.class, TestUtil.createRootProject(projectDir).getServices()); - assertThat(bean, instanceOf(Managed.class)); - - Managed managed = (Managed) bean; - assertEquals(InterfaceFileCollectionBean.class, managed.publicType()); - assertFalse(managed.immutable()); - Object[] state = (Object[]) managed.unpackState(); - assertEquals(1, state.length); - assertTrue(state[0] instanceof ConfigurableFileCollection); - assertSame(state[0], bean.getFiles()); - - InterfaceFileCollectionBean copy = managed.managedFactory().fromState(InterfaceFileCollectionBean.class, state); - assertTrue(copy.getFiles().isEmpty()); - } - - @Test - public void canConstructInstanceOfInterfaceWithDefaultMethodsOnly() throws Exception { - InterfaceWithDefaultMethods bean = newInstance(InterfaceWithDefaultMethods.class); - - assertThat(bean.getName(), equalTo("name")); - } - - @Test - public void canUnpackAndRecreateInstanceOfInterfaceWithDefaultMethodsOnly() throws Exception { - InterfaceWithDefaultMethods bean = newInstance(InterfaceWithDefaultMethods.class); - assertThat(bean, instanceOf(Managed.class)); - - Managed managed = (Managed) bean; - assertEquals(InterfaceWithDefaultMethods.class, managed.publicType()); - assertTrue(managed.immutable()); // no properties - Object[] state = (Object[]) managed.unpackState(); - assertThat(state.length, equalTo(0)); - - InterfaceWithDefaultMethods copy = managed.managedFactory().fromState(InterfaceWithDefaultMethods.class, state); - assertThat(copy, not(nullValue())); - } - - @Test - public void doesNotMixManagedIntoClassWithFields() throws Exception { - Bean bean = newInstance(Bean.class); - assertThat(bean, not(instanceOf(Managed.class))); - } - - @Test - public void doesNotMixManagedIntoAbstractClassWithFields() throws Exception { - AbstractBean bean = newInstance(AbstractBean.class, "value"); - assertThat(bean, not(instanceOf(Managed.class))); - } - - @Test - public void doesNotMixManagedIntoClassWithInheritedFields() throws Exception { - AbstractBeanWithInheritedFields bean = newInstance(AbstractBeanWithInheritedFields.class, "value"); - assertThat(bean, not(instanceOf(Managed.class))); - } @Test public void cannotCreateInstanceOfInterfaceWithAbstractGetterAndNoSetter() throws Exception { @@ -1818,13 +1687,6 @@ public Property getaProp() { } } - public static class InjectPropertyBean { - @Inject - public Property getProp() { - throw new UnsupportedOperationException(); - } - } - public interface WithProperties { Number getNumber(); } @@ -1890,8 +1752,58 @@ public interface InterfaceBean { void setNumbers(Set values); } + public interface InterfacePrimitiveBean { + boolean isProp1(); + + void setProp1(boolean value); + + int getProp2(); + + void setProp2(int value); + + byte getProp3(); + + void setProp3(byte value); + + short getProp4(); + + void setProp4(short value); + + long getProp5(); + + void setProp5(long value); + + double getProp6(); + + void setProp6(double value); + } + public interface InterfaceFileCollectionBean { - ConfigurableFileCollection getFiles(); + ConfigurableFileCollection getProp(); + } + + public interface InterfacePropertyBean { + Property getProp(); + } + + public interface InterfaceFilePropertyBean { + RegularFileProperty getProp(); + } + + public interface InterfaceDirectoryPropertyBean { + DirectoryProperty getProp(); + } + + public interface InterfaceListPropertyBean { + ListProperty getProp(); + } + + public interface InterfaceSetPropertyBean { + SetProperty getProp(); + } + + public interface InterfaceMapPropertyBean { + MapProperty getProp(); } public interface InterfaceWithDefaultMethods { @@ -1913,4 +1825,36 @@ void thing() { setName("thing"); } } + + interface InterfaceWithTypeParameter { + @Inject + T getThing(); + } + + public static abstract class AbstractClassWithConcreteTypeParameter implements InterfaceWithTypeParameter { + public String doSomething() { + Number thing = getThing(); + return String.valueOf(thing); + } + } + + public static abstract class AbstractClassWithParameterizedTypeParameter implements InterfaceWithTypeParameter> { + public String doSomething() { + List thing = getThing(); + return thing.toString(); + } + } + + public static abstract class AbstractClassWithTwoTypeParameters implements InterfaceWithTypeParameter { + @Inject + public abstract List getOtherThing(); + } + + public static abstract class AbstractClassRealizingTwoTypeParameters extends AbstractClassWithTwoTypeParameters { + public String doSomething() { + Number thing = getThing(); + List otherThing = getOtherThing(); + return Joiner.on(" ").join(otherThing) + " " + thing; + } + } } diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/DefaultInstantiatorFactoryTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/DefaultInstantiatorFactoryTest.groovy index f8493ee7b8366..15337456bb377 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/DefaultInstantiatorFactoryTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/DefaultInstantiatorFactoryTest.groovy @@ -19,6 +19,7 @@ package org.gradle.internal.instantiation import org.gradle.cache.internal.TestCrossBuildInMemoryCacheFactory import spock.lang.Specification +import javax.inject.Inject import java.lang.annotation.Annotation import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy @@ -27,18 +28,9 @@ import java.lang.annotation.RetentionPolicy class DefaultInstantiatorFactoryTest extends Specification { def instantiatorFactory = new DefaultInstantiatorFactory(new TestCrossBuildInMemoryCacheFactory(), [handler(Annotation1), handler(Annotation2)]) - def "caches InstantiationScheme instances"() { - def classes = [Annotation1] - + def "creates scheme with requested annotations"() { expect: - instantiatorFactory.injectScheme(classes).is(instantiatorFactory.injectScheme(classes)) - instantiatorFactory.injectScheme(classes as Set).is(instantiatorFactory.injectScheme(classes)) - instantiatorFactory.injectScheme([Annotation1, Annotation1]).is(instantiatorFactory.injectScheme(classes)) - - !instantiatorFactory.injectScheme(classes).is(instantiatorFactory.injectScheme([])) - !instantiatorFactory.injectScheme(classes).is(instantiatorFactory.injectScheme([Annotation1, Annotation2])) - - instantiatorFactory.injectScheme([Annotation2, Annotation1]).is(instantiatorFactory.injectScheme([Annotation1, Annotation2])) + instantiatorFactory.injectScheme([Annotation1]).injectionAnnotations == [Annotation1, Inject] as Set } def "uses Instantiators from inject schemes"() { @@ -57,12 +49,6 @@ class DefaultInstantiatorFactoryTest extends Specification { e.message == 'Annotation @Annotation3 is not a registered injection annotation.' } - def handler(Class annotation) { - InjectAnnotationHandler handler = Stub(InjectAnnotationHandler) - handler.annotation >> annotation - return handler - } - def "detects properties injected by annotation"() { def scheme = instantiatorFactory.injectScheme([Annotation1, Annotation2]) when: @@ -77,6 +63,12 @@ class DefaultInstantiatorFactoryTest extends Specification { instanceFactory.serviceInjectionTriggeredByAnnotation(Annotation1) instanceFactory.serviceInjectionTriggeredByAnnotation(Annotation2) } + + def handler(Class annotation) { + InjectAnnotationHandler handler = Stub(InjectAnnotationHandler) + handler.annotationType >> annotation + return handler + } } @Retention(RetentionPolicy.RUNTIME) diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/DependencyInjectionUsingClassGeneratorBackedInstantiatorTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/DependencyInjectionUsingClassGeneratorBackedInstantiatorTest.groovy index 75dab15a4ef27..d3f13bde98963 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/DependencyInjectionUsingClassGeneratorBackedInstantiatorTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/instantiation/DependencyInjectionUsingClassGeneratorBackedInstantiatorTest.groovy @@ -17,7 +17,9 @@ package org.gradle.internal.instantiation import org.gradle.cache.internal.CrossBuildInMemoryCache import org.gradle.cache.internal.TestCrossBuildInMemoryCacheFactory +import org.gradle.internal.service.DefaultServiceRegistry import org.gradle.internal.service.ServiceLookup +import org.gradle.util.TestUtil import spock.lang.Specification import javax.inject.Inject @@ -25,12 +27,16 @@ import javax.inject.Inject class DependencyInjectionUsingClassGeneratorBackedInstantiatorTest extends Specification { final ClassGenerator classGenerator = AsmBackedClassGenerator.decorateAndInject([], []) final CrossBuildInMemoryCache cache = new TestCrossBuildInMemoryCacheFactory().newCache() - final ServiceLookup services = Mock() + final ServiceLookup services = new DefaultServiceRegistry() final DependencyInjectingInstantiator instantiator = new DependencyInjectingInstantiator(new Jsr330ConstructorSelector(classGenerator, cache), services) + def setup() { + services.add(InstantiatorFactory, TestUtil.instantiatorFactory()) + } + def "injects service using getter injection"() { given: - _ * services.get(String) >> "string" + services.add(String, "string") when: def result = instantiator.newInstance(HasGetterInjection) @@ -41,7 +47,7 @@ class DependencyInjectionUsingClassGeneratorBackedInstantiatorTest extends Speci def "injects service using abstract getter injection"() { given: - _ * services.get(String) >> "string" + services.add(String, "string") when: def result = instantiator.newInstance(AbstractHasGetterInjection) @@ -52,7 +58,7 @@ class DependencyInjectionUsingClassGeneratorBackedInstantiatorTest extends Speci def "constructor can use getter injected service"() { given: - _ * services.get(String) >> "string" + services.add(String, "string") when: def result = instantiator.newInstance(UsesInjectedServiceFromConstructor) @@ -64,7 +70,7 @@ class DependencyInjectionUsingClassGeneratorBackedInstantiatorTest extends Speci def "constructor can receive injected service and parameter"() { given: - _ * services.find(String) >> "string" + services.add(String, "string") when: def result = instantiator.newInstance(HasInjectConstructor, 12) @@ -76,9 +82,7 @@ class DependencyInjectionUsingClassGeneratorBackedInstantiatorTest extends Speci def "can use factory to create instance with injected service and parameter"() { given: - def services = Stub(ServiceLookup) - _ * services.find(String) >> "string" - _ * services.find(_) >> null + services.add(String, "string") when: def factory = instantiator.factoryFor(HasInjectConstructor) @@ -91,9 +95,7 @@ class DependencyInjectionUsingClassGeneratorBackedInstantiatorTest extends Speci def "can use factory to create instance with injected service using getter"() { given: - def services = Stub(ServiceLookup) - _ * services.get(String) >> "string" - _ * services.find(_) >> null + services.add(String, "string") when: def factory = instantiator.factoryFor(HasGetterInjection) diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/JavaPropertyReflectionUtilTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/JavaPropertyReflectionUtilTest.groovy index 16fa3f2173474..db26258cad21f 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/JavaPropertyReflectionUtilTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/JavaPropertyReflectionUtilTest.groovy @@ -18,10 +18,12 @@ package org.gradle.internal.reflect import spock.lang.Specification +import spock.lang.Unroll import java.lang.annotation.Inherited import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy +import java.lang.reflect.Type import static JavaPropertyReflectionUtil.getAnnotation import static JavaPropertyReflectionUtil.hasDefaultToString @@ -262,6 +264,46 @@ class JavaPropertyReflectionUtilTest extends Specification { expect: !hasDefaultToString(new ClassWithToString()) } + + @Unroll + def "#type has type variable: #hasTypeVariable"() { + expect: + JavaPropertyReflectionUtil.hasTypeVariable(type) == hasTypeVariable + + where: + testType << testedTypes + type = testType.first + hasTypeVariable = testType.second + } + + @Unroll + def "#method.genericReturnType resolves to #expectedResolvedReturnType"() { + def resolvedReturnType = JavaPropertyReflectionUtil.resolveMethodReturnType(JavaPropertyReflectionUtilTestMethods.InterfaceRealizingTypeParameter, method) + expect: + resolvedReturnType.toString() == expectedResolvedReturnType + + where: + method << (JavaPropertyReflectionUtilTestMethods.getDeclaredMethods() as List) + expectedResolvedReturnType = method.genericReturnType.toString().replace("T", "java.util.List") + } + + @Unroll + def "#method.genericReturnType is not resolved if declared on same class"() { + def resolvedReturnType = JavaPropertyReflectionUtil.resolveMethodReturnType(JavaPropertyReflectionUtilTestMethods, method) + expect: + resolvedReturnType == method.genericReturnType + + where: + method << (JavaPropertyReflectionUtilTestMethods.getDeclaredMethods() as List) + } + + private static List> getTestedTypes() { + def testedTypes = JavaPropertyReflectionUtilTestMethods.getDeclaredMethods().collect { + new Tuple2(it.genericReturnType, it.name.contains('TypeVariable')) + } + assert testedTypes.size() == 16 + return testedTypes + } } @Retention(RetentionPolicy.RUNTIME) diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/JavaPropertyReflectionUtilTestMethods.java b/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/JavaPropertyReflectionUtilTestMethods.java new file mode 100644 index 0000000000000..c525151818f30 --- /dev/null +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/JavaPropertyReflectionUtilTestMethods.java @@ -0,0 +1,58 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.internal.reflect; + +import java.util.Collection; +import java.util.List; +import java.util.function.BiConsumer; + +public interface JavaPropertyReflectionUtilTestMethods { + T simpleTypeVariable(); + + List encapsulatedTypeVariable(); + + T[] arrayTypeWithTypeVariable(); + + List>[]> complexTypeWithTypeVariable(); + + List>, ? extends List>[]> anotherComplexTypeWithTypeVariable(); + + List>, ? extends List>> complexTypeWithArrayTypeVariable(); + + Class wildcardParameterized(); + + Class parameterized(); + + Class[] genericArrayType(); + + String simpleType(); + + String[] simpleArrayType(); + + List>> complexParameterized(); + + List> wildCardWithlowerBound(); + + List> wildCardWithUpperBound(); + + BiConsumer, List> anotherComplexParameterized(); + + V nonResolvedTypeVariable(); + + interface InterfaceRealizingTypeParameter extends JavaPropertyReflectionUtilTestMethods> { + } +} diff --git a/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/PropertyExtractorTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/PropertyExtractorTest.groovy index cba7fc875e4ec..4a5f1bf431b86 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/PropertyExtractorTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/internal/reflect/PropertyExtractorTest.groovy @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableMultimap import com.google.common.collect.ImmutableSet import org.gradle.api.file.FileCollection import org.gradle.api.internal.tasks.properties.DefaultParameterValidationContext +import org.gradle.internal.Pair import spock.lang.Issue import spock.lang.Specification @@ -53,9 +54,9 @@ class PropertyExtractorTest extends Specification { def "can override property type in subclasses"() { expect: - extractor.extractPropertyMetadata(WithPropertyType1).left*.propertyType == [PropertyType1] - extractor.extractPropertyMetadata(WithPropertyType2).left*.propertyType == [PropertyType2] - extractor.extractPropertyMetadata(WithPropertyOverride).left*.propertyType == [PropertyType1Override] + extract(WithPropertyType1)*.propertyType == [PropertyType1] + extract(WithPropertyType2)*.propertyType == [PropertyType2] + extract(WithPropertyOverride)*.propertyType == [PropertyType1Override] } class OverridingProperties { @@ -65,11 +66,10 @@ class PropertyExtractorTest extends Specification { def "overriding annotation on same property takes effect"() { when: - def result = extractor.extractPropertyMetadata(OverridingProperties) + def result = extract(OverridingProperties) then: - assertPropertyTypes(result.left, inputFiles1: PropertyType1Override, inputFiles2: PropertyType1Override) - result.right.empty + assertPropertyTypes(result, inputFiles1: PropertyType1Override, inputFiles2: PropertyType1Override) } class BasePropertyType1OverrideProperty { @@ -94,11 +94,10 @@ class PropertyExtractorTest extends Specification { @Issue("https://github.com/gradle/gradle/issues/913") def "overriding annotation does not take precedence in sub-type"() { when: - def result = extractor.extractPropertyMetadata(OverridingPropertyType1Property) + def result = extract(OverridingPropertyType1Property) then: - assertPropertyTypes(result.left, overriddenType1Override: PropertyType1, overriddenType1: PropertyType1Override) - result.right.empty + assertPropertyTypes(result, overriddenType1Override: PropertyType1, overriddenType1: PropertyType1Override) } class WithBothFieldAndGetterAnnotation { @@ -112,11 +111,11 @@ class PropertyExtractorTest extends Specification { def "warns about both method and field having the same annotation"() { when: - def result = extractor.extractPropertyMetadata(WithBothFieldAndGetterAnnotation) + def result = extractWithProblems(WithBothFieldAndGetterAnnotation) then: assertPropertyTypes(result.left, inputFiles: PropertyType1) - collectProblems(result.right) == ["Property 'inputFiles' has both a getter and field declared with annotation @${PropertyType1.simpleName}."] + result.right == ["Property 'inputFiles' has both a getter and field declared with annotation @${PropertyType1.simpleName}."] } class WithBothFieldAndGetterAnnotationButIrrelevant { @@ -130,11 +129,10 @@ class PropertyExtractorTest extends Specification { def "doesn't warn about both method and field having the same irrelevant annotation"() { when: - def result = extractor.extractPropertyMetadata(WithBothFieldAndGetterAnnotationButIrrelevant) + def result = extract(WithBothFieldAndGetterAnnotationButIrrelevant) then: - assertPropertyTypes(result.left, inputFiles: PropertyType1) - result.right.empty + assertPropertyTypes(result, inputFiles: PropertyType1) } class WithAnnotationsOnPrivateProperties { @@ -155,11 +153,11 @@ class PropertyExtractorTest extends Specification { def "warns about annotations on private properties"() { when: - def result = extractor.extractPropertyMetadata(WithAnnotationsOnPrivateProperties) + def result = extractWithProblems(WithAnnotationsOnPrivateProperties) then: assertPropertyTypes(result.left, input: PropertyType1, outputFile: PropertyType2) - collectProblems(result.right) == [ + result.right == [ "Property 'input' is private and annotated with @${PropertyType1.simpleName}.", "Property 'outputFile' is private and annotated with @${PropertyType2.simpleName}." ] @@ -181,11 +179,11 @@ class PropertyExtractorTest extends Specification { def "warns about non-private getters that are not annotated"() { when: - def result = extractor.extractPropertyMetadata(WithUnannotatedProperties) + def result = extractWithProblems(WithUnannotatedProperties) then: result.left.empty - collectProblems(result.right) == [ + result.right == [ "Property 'bad1' is not annotated with a thing annotation.", "Property 'bad2' is not annotated with a thing annotation." ] @@ -203,11 +201,11 @@ class PropertyExtractorTest extends Specification { def "warns about conflicting property types being specified"() { when: - def result = extractor.extractPropertyMetadata(WithConflictingPropertyTypes) + def result = extractWithProblems(WithConflictingPropertyTypes) then: assertPropertyTypes(result.left, inputThing: PropertyType1, confusedFile: PropertyType2) - collectProblems(result.right) == [ + result.right == [ "Property 'confusedFile' has conflicting property types declared: @${PropertyType1.simpleName}, @${PropertyType2.simpleName}.", "Property 'inputThing' has conflicting property types declared: @${PropertyType1.simpleName}, @${PropertyType2.simpleName}." ] @@ -224,11 +222,11 @@ class PropertyExtractorTest extends Specification { def "warns about properties annotated with known bu unsupported annotations"() { when: - def result = extractor.extractPropertyMetadata(WithUnsupportedPropertyTypes) + def result = extractWithProblems(WithUnsupportedPropertyTypes) then: assertPropertyTypes(result.left, hasBoth: PropertyType1) - collectProblems(result.right) == [ + result.right == [ "Property 'hasBoth' is annotated with unsupported annotation @${KnownAnnotation.simpleName}.", "Property 'inputThing' is annotated with unsupported annotation @${KnownAnnotation.simpleName}." ] @@ -242,11 +240,10 @@ class PropertyExtractorTest extends Specification { def "doesn't warn about non-conflicting property types being specified"() { when: - def result = extractor.extractPropertyMetadata(WithNonConflictingPropertyTypes) + def result = extract(WithNonConflictingPropertyTypes) then: - assertPropertyTypes(result.left, classpath: PropertyType1Override) - result.right.empty + assertPropertyTypes(result, classpath: PropertyType1Override) } static class SimpleType { @@ -259,7 +256,7 @@ class PropertyExtractorTest extends Specification { def "can get annotated properties of simple type"() { when: - def result = extractor.extractPropertyMetadata(SimpleType) + def result = extractWithProblems(SimpleType) then: assertPropertyTypes(result.left, @@ -267,7 +264,7 @@ class PropertyExtractorTest extends Specification { inputFile: PropertyType1Override, inputDirectory: PropertyType2 ) - collectProblems(result.right) == ["Property 'injectedService' is not annotated with a thing annotation."] + result.right == ["Property 'injectedService' is not annotated with a thing annotation."] } static abstract class BaseClassWithGetters { @@ -295,11 +292,10 @@ class PropertyExtractorTest extends Specification { def "annotations are gathered from different getters"() { when: - def result = extractor.extractPropertyMetadata(WithGetters) + def result = extract(WithGetters) then: - assertPropertyTypes(result.left, boolean: PropertyType1, strings: PropertyType2) - result.right.empty + assertPropertyTypes(result, boolean: PropertyType1, strings: PropertyType2) } private static class BaseType { @@ -328,16 +324,15 @@ class PropertyExtractorTest extends Specification { def "overridden properties inherit super-class annotations"() { when: - def result = extractor.extractPropertyMetadata(OverridingType) + def result = extract(OverridingType) then: - assertPropertyTypes(result.left, + assertPropertyTypes(result, baseValue: PropertyType1, nonAnnotatedBaseValue: PropertyType1, superclassValue: PropertyType1, superclassValueWithDuplicateAnnotation: PropertyType1, ) - result.right.empty } private interface TaskSpec { @@ -354,13 +349,12 @@ class PropertyExtractorTest extends Specification { def "implemented properties inherit interface annotations"() { when: - def result = extractor.extractPropertyMetadata(InterfaceImplementingType) + def result = extract(InterfaceImplementingType) then: - assertPropertyTypes(result.left, + assertPropertyTypes(result, interfaceValue: PropertyType1 ) - result.right.empty } @SuppressWarnings("GroovyUnusedDeclaration") @@ -386,22 +380,26 @@ class PropertyExtractorTest extends Specification { @Issue("https://issues.gradle.org/browse/GRADLE-2115") def "annotation on private field is recognized for is-getter"() { when: - def result = extractor.extractPropertyMetadata(IsGetterType) + def result = extractWithProblems(IsGetterType) then: assertPropertyTypes(result.left, feature1: PropertyType1 ) - collectProblems(result.right) == ["Property 'feature2' is not annotated with a thing annotation."] + result.right == ["Property 'feature2' is not annotated with a thing annotation."] } - private static List collectProblems(Collection problems) { - def result = [] - def context = new DefaultParameterValidationContext(result) - problems.forEach { - it.collect(null, context) - } - return result + private Set extract(Class type) { + def problems = [] + def props = extractor.extractPropertyMetadata(type, new DefaultParameterValidationContext(problems)) + assert problems.empty + return props + } + + private Pair, List> extractWithProblems(Class type) { + def problems = [] + def props = extractor.extractPropertyMetadata(type, new DefaultParameterValidationContext(problems)) + return Pair.of(props, problems) } private static void assertPropertyTypes(Map expectedPropertyTypes, Set typeMetadata) { diff --git a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/type/ModelTypesTest.groovy b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/type/ModelTypesTest.groovy index a2646efae252a..c61aac05db648 100644 --- a/subprojects/model-core/src/test/groovy/org/gradle/model/internal/type/ModelTypesTest.groovy +++ b/subprojects/model-core/src/test/groovy/org/gradle/model/internal/type/ModelTypesTest.groovy @@ -16,6 +16,7 @@ package org.gradle.model.internal.type +import org.gradle.api.JavaVersion import spock.lang.Specification import spock.lang.Unroll @@ -27,9 +28,24 @@ class ModelTypesTest extends Specification { where: types | closed - [Integer] | [Integer, Number, Comparable, Serializable] - [Integer, Double] | [Integer, Double, Number, Comparable, Serializable] + [Integer] | typeHierarchyForInteger + [Integer, Double] | typeHierarchyForInteger + typeHierarchyForDouble [Object] | [] [GroovyObject] | [] } + + static def getTypeHierarchyForDouble() { + return maybeWithJava12ConstantTypes([Double, Number, Comparable, Serializable]) + } + + static def getTypeHierarchyForInteger() { + return maybeWithJava12ConstantTypes([Integer, Number, Comparable, Serializable]) + } + + static def maybeWithJava12ConstantTypes(types) { + if (JavaVersion.current().java12Compatible) { + types += [Class.forName("java.lang.constant.Constable"), Class.forName("java.lang.constant.ConstantDesc")] + } + return types + } } diff --git a/subprojects/model-groovy/model-groovy.gradle.kts b/subprojects/model-groovy/model-groovy.gradle.kts index 3be14d2a2d536..ea7a4b6bd62e6 100644 --- a/subprojects/model-groovy/model-groovy.gradle.kts +++ b/subprojects/model-groovy/model-groovy.gradle.kts @@ -31,7 +31,6 @@ dependencies { api(library("groovy")) implementation(project(":baseServicesGroovy")) - implementation(library("jcip")) implementation(library("guava")) } diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/NonTransformedModelDslBacking.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/NonTransformedModelDslBacking.java index 158d2950fbcad..f5261c7218578 100644 --- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/NonTransformedModelDslBacking.java +++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/NonTransformedModelDslBacking.java @@ -20,7 +20,7 @@ import groovy.lang.GroovyObjectSupport; import groovy.lang.MissingMethodException; import groovy.lang.MissingPropertyException; -import net.jcip.annotations.NotThreadSafe; +import javax.annotation.concurrent.NotThreadSafe; import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.internal.Actions; diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/TransformedModelDslBacking.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/TransformedModelDslBacking.java index ee274be1b6c1d..4ea0a034e6e84 100644 --- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/TransformedModelDslBacking.java +++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/TransformedModelDslBacking.java @@ -17,7 +17,7 @@ package org.gradle.model.dsl.internal; import groovy.lang.Closure; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Action; import org.gradle.internal.file.RelativeFilePathResolver; import org.gradle.model.InvalidModelRuleDeclarationException; diff --git a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/ClosureCreationInterceptingVerifier.java b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/ClosureCreationInterceptingVerifier.java index c01ebbf474062..ddbc0e61bbde5 100644 --- a/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/ClosureCreationInterceptingVerifier.java +++ b/subprojects/model-groovy/src/main/java/org/gradle/model/dsl/internal/transform/ClosureCreationInterceptingVerifier.java @@ -16,7 +16,7 @@ package org.gradle.model.dsl.internal.transform; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.gradle.api.Action; diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/ConsoleMetaData.java b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/ConsoleMetaData.java index 62a77f43222fa..6c0cb3a2ba13f 100644 --- a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/ConsoleMetaData.java +++ b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/ConsoleMetaData.java @@ -40,4 +40,6 @@ public interface ConsoleMetaData { * @return The height of the console (rows). If no information is available return 0. */ int getRows(); + + boolean isWrapStreams(); } diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/FallbackConsoleMetaData.java b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/FallbackConsoleMetaData.java index 67528711c7bd3..0902f614378c8 100644 --- a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/FallbackConsoleMetaData.java +++ b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/FallbackConsoleMetaData.java @@ -45,4 +45,9 @@ public int getCols() { public int getRows() { return 0; } + + @Override + public boolean isWrapStreams() { + return attached; + } } diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/NativePlatformConsoleMetaData.java b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/NativePlatformConsoleMetaData.java index f7ae2077dc5d6..38458dcc0ab33 100644 --- a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/NativePlatformConsoleMetaData.java +++ b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/NativePlatformConsoleMetaData.java @@ -48,4 +48,9 @@ public int getCols() { public int getRows() { return terminal.getTerminalSize().getRows(); } + + @Override + public boolean isWrapStreams() { + return true; + } } diff --git a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/TestConsoleMetadata.java b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/TestConsoleMetadata.java index 721a06262e64b..0086e07955c87 100644 --- a/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/TestConsoleMetadata.java +++ b/subprojects/native/src/main/java/org/gradle/internal/nativeintegration/console/TestConsoleMetadata.java @@ -18,7 +18,6 @@ public enum TestConsoleMetadata implements ConsoleMetaData { BOTH(true, true), - NEITHER(false, false), STDOUT_ONLY(true, false), STDERR_ONLY(false, true); @@ -52,6 +51,11 @@ public int getRows() { return 40; } + @Override + public boolean isWrapStreams() { + return false; + } + public String getCommandLineArgument() { return "-D" + TEST_CONSOLE_PROPERTY + "=" + name(); } diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/android/RealLifeAndroidBuildPerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/android/RealLifeAndroidBuildPerformanceTest.groovy index 38c3caea79196..2551ef930e0f9 100644 --- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/android/RealLifeAndroidBuildPerformanceTest.groovy +++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/android/RealLifeAndroidBuildPerformanceTest.groovy @@ -29,8 +29,8 @@ class RealLifeAndroidBuildPerformanceTest extends AbstractAndroidPerformanceTest runner.args = parallel ? ['-Dorg.gradle.parallel=true'] : [] runner.warmUpRuns = warmUpRuns runner.runs = runs - runner.minimumVersion = "4.3.1" - runner.targetVersions = ["5.2-20181218000039+0000"] + runner.minimumVersion = "5.1.1" + runner.targetVersions = ["5.4-20190311000052+0000"] when: def result = runner.run() @@ -40,11 +40,12 @@ class RealLifeAndroidBuildPerformanceTest extends AbstractAndroidPerformanceTest where: testProject | memory | parallel | warmUpRuns | runs | tasks - 'k9AndroidBuild' | '1g' | false | null | null | 'help' - 'k9AndroidBuild' | '1g' | false | null | null | 'assembleDebug' + 'k9AndroidBuild' | '1g' | false | null | null | 'help' + 'k9AndroidBuild' | '1g' | false | null | null | 'assembleDebug' // 'k9AndroidBuild' | '1g' | false | null | null | 'clean k9mail:assembleDebug' - 'largeAndroidBuild' | '5g' | true | null | null | 'help' - 'largeAndroidBuild' | '5g' | true | null | null | 'assembleDebug' - 'largeAndroidBuild' | '5g' | true | 2 | 8 | 'clean phthalic:assembleDebug' + 'largeAndroidBuild' | '5g' | true | null | null | 'help' + 'largeAndroidBuild' | '5g' | true | null | null | 'assembleDebug' + 'largeAndroidBuild' | '5g' | true | 2 | 8 | 'clean phthalic:assembleDebug' + 'santaTrackerAndroidBuild' | '1g' | true | null | null | 'assembleDebug' } } diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/android/RealLifeAndroidStudioMockupPerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/android/RealLifeAndroidStudioMockupPerformanceTest.groovy index 5eef3a926d40f..9111a53106819 100644 --- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/android/RealLifeAndroidStudioMockupPerformanceTest.groovy +++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/android/RealLifeAndroidStudioMockupPerformanceTest.groovy @@ -27,7 +27,7 @@ class RealLifeAndroidStudioMockupPerformanceTest extends AbstractAndroidStudioMo experiment(testProject) { minimumVersion = "4.3.1" - targetVersions = ["5.3-20190122101802+0000"] + targetVersions = ["5.4-20190329080509+0000"] action('org.gradle.performance.android.SyncAction') { jvmArguments = ["-Xms5g", "-Xmx5g"] } diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/buildcache/TaskOutputCachingJavaPerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/buildcache/TaskOutputCachingJavaPerformanceTest.groovy index ca2e530eee1f9..a12d1187de3a2 100644 --- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/buildcache/TaskOutputCachingJavaPerformanceTest.groovy +++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/buildcache/TaskOutputCachingJavaPerformanceTest.groovy @@ -42,7 +42,7 @@ class TaskOutputCachingJavaPerformanceTest extends AbstractTaskOutputCachingPerf runner.warmUpRuns = 11 runner.runs = 21 runner.minimumVersion = "3.5" - runner.targetVersions = ["5.2-20181218000039+0000"] + runner.targetVersions = ["5.4-20190329080509+0000"] } def "clean #tasks on #testProject with remote http cache"() { diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/inception/GradleInceptionPerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/inception/GradleInceptionPerformanceTest.groovy index c2315550fed93..fe5ae611c1adc 100644 --- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/inception/GradleInceptionPerformanceTest.groovy +++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/inception/GradleInceptionPerformanceTest.groovy @@ -52,7 +52,7 @@ class GradleInceptionPerformanceTest extends AbstractCrossVersionPerformanceTest } def setup() { - def targetVersion = "5.3-20190201000727+0000" + def targetVersion = "5.4-20190322154513+0000" runner.targetVersions = [targetVersion] runner.minimumVersion = targetVersion } @@ -82,7 +82,7 @@ class GradleInceptionPerformanceTest extends AbstractCrossVersionPerformanceTest runner.testProject = testProject runner.tasksToRun = ['help'] runner.runs = runs - runner.args = extraGradleBuildArguments() + ["-Pgradlebuild.skipBuildSrcChecks=true"] + runner.args = extraGradleBuildArguments() + ["-PbuildSrcCheck=false"] and: def changingClassFilePath = "buildSrc/${buildSrcProjectDir}src/main/groovy/ChangingClass.groovy" diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaABIChangePerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaABIChangePerformanceTest.groovy index d292dbd6caa20..ca6352da6eeb9 100644 --- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaABIChangePerformanceTest.groovy +++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaABIChangePerformanceTest.groovy @@ -32,7 +32,7 @@ class JavaABIChangePerformanceTest extends AbstractCrossVersionPerformanceTest { runner.gradleOpts = ["-Xms${testProject.daemonMemory}", "-Xmx${testProject.daemonMemory}"] runner.tasksToRun = ['assemble'] runner.addBuildExperimentListener(new ApplyAbiChangeToJavaSourceFileMutator(testProject.config.fileToChangeByScenario['assemble'])) - runner.targetVersions = ["5.3-20190128115559+0000"] + runner.targetVersions = ["5.4-20190329080509+0000"] when: def result = runner.run() diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaFirstUsePerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaFirstUsePerformanceTest.groovy index 97fe13f59d1cc..d925cd2f88ca4 100644 --- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaFirstUsePerformanceTest.groovy +++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaFirstUsePerformanceTest.groovy @@ -73,7 +73,7 @@ class JavaFirstUsePerformanceTest extends AbstractCrossVersionPerformanceTest { runner.gradleOpts = ["-Xms${testProject.daemonMemory}", "-Xmx${testProject.daemonMemory}"] runner.tasksToRun = ['tasks'] runner.useDaemon = false - runner.targetVersions = ["5.3-20190131161420+0000"] + runner.targetVersions = ["5.4-20190314000100+0000"] runner.addBuildExperimentListener(new BuildExperimentListenerAdapter() { @Override void afterInvocation(BuildExperimentInvocationInfo invocationInfo, MeasuredOperation operation, BuildExperimentListener.MeasurementCallback measurementCallback) { @@ -104,7 +104,7 @@ class JavaFirstUsePerformanceTest extends AbstractCrossVersionPerformanceTest { runner.gradleOpts = ["-Xms${testProject.daemonMemory}", "-Xmx${testProject.daemonMemory}"] runner.tasksToRun = ['tasks'] runner.useDaemon = false - runner.targetVersions = ["5.3-20190131161420+0000"] + runner.targetVersions = ["5.4-20190314000100+0000"] when: def result = runner.run() diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaNonABIChangePerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaNonABIChangePerformanceTest.groovy index 64f397f1a2a35..db6604ff2653c 100644 --- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaNonABIChangePerformanceTest.groovy +++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaNonABIChangePerformanceTest.groovy @@ -32,7 +32,7 @@ class JavaNonABIChangePerformanceTest extends AbstractCrossVersionPerformanceTes runner.gradleOpts = ["-Xms${testProject.daemonMemory}", "-Xmx${testProject.daemonMemory}"] runner.tasksToRun = ['assemble'] runner.addBuildExperimentListener(new ApplyNonAbiChangeToJavaSourceFileMutator(testProject.config.fileToChangeByScenario['assemble'])) - runner.targetVersions = ["5.3-20190128115559+0000"] + runner.targetVersions = ["5.4-20190329080509+0000"] when: def result = runner.run() diff --git a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaUpToDatePerformanceTest.groovy b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaUpToDatePerformanceTest.groovy index 0fe7dd7833f9c..92629c7cedf85 100644 --- a/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaUpToDatePerformanceTest.groovy +++ b/subprojects/performance/src/performanceTest/groovy/org/gradle/performance/regression/java/JavaUpToDatePerformanceTest.groovy @@ -34,7 +34,7 @@ class JavaUpToDatePerformanceTest extends AbstractCrossVersionPerformanceTest { runner.testProject = testProject runner.gradleOpts = ["-Xms${testProject.daemonMemory}", "-Xmx${testProject.daemonMemory}"] runner.tasksToRun = ['assemble'] - runner.targetVersions = ["5.2-20181218000039+0000"] + runner.targetVersions = ["5.4-20190329080509+0000"] runner.args += ["-Dorg.gradle.parallel=$parallel"] when: @@ -56,7 +56,7 @@ class JavaUpToDatePerformanceTest extends AbstractCrossVersionPerformanceTest { runner.testProject = testProject runner.gradleOpts = ["-Xms${testProject.daemonMemory}", "-Xmx${testProject.daemonMemory}"] runner.tasksToRun = ['assemble'] - runner.targetVersions = ["5.2-20181218000039+0000"] + runner.targetVersions = ["5.4-20190329080509+0000"] runner.minimumVersion = "3.5" runner.args += ["-Dorg.gradle.parallel=$parallel", "-D${StartParameterBuildOptions.BuildCacheOption.GRADLE_PROPERTY}=true"] def cacheDir = temporaryFolder.file("local-cache") diff --git a/subprojects/performance/templates.gradle b/subprojects/performance/templates.gradle index 1aaec22ea7da5..a84a4211b3793 100644 --- a/subprojects/performance/templates.gradle +++ b/subprojects/performance/templates.gradle @@ -27,7 +27,7 @@ tasks.register("gradleBuildBaseline", RemoteProject) { remoteUri = rootDir.absolutePath // Remember to update accordingly when rebasing/squashing // Do not use the "Rebase and merge" nor "Squash and merge" Github buttons when merging a PR that change the baseline - ref = 'df9851e45acebbba9fa1e3fe2194b0e5346c20dd' + ref = 'c9913554c6032894bb4433789db2ee87a801123e' } // === Java === @@ -354,6 +354,88 @@ tasks.register("largeAndroidBuild", RemoteProject) { branch = 'android-34' } +tasks.register("santaTrackerAndroidBuild", RemoteProject) { + remoteUri = 'https://github.com/gradle/santa-tracker-android.git' + branch = 'agp-3.5.0' + doLast { + new File(outputDirectory, 'santa-tracker/google-services.json').text = """ +{ + "project_info": { + "project_number": "012345678912", + "firebase_url": "https://example.com", + "project_id": "example", + "storage_bucket": "example.example.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:012345678912:android:0123456789abcdef", + "android_client_info": { + "package_name": "com.google.android.apps.santatracker.debug" + } + }, + "oauth_client": [ + { + "client_id": "foo.example.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "012345678901234567890123456789012345678" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:012345678912:android:0123456789abcdef", + "android_client_info": { + "package_name": "com.google.android.apps.santatracker.debug" + } + }, + "oauth_client": [ + { + "client_id": "foo.example.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "012345678901234567890123456789012345678" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} +""" + } +} + tasks.register("excludeRuleMergingBuild", RemoteProject) { remoteUri = 'https://github.com/gradle/performance-comparisons.git' branch = 'master' diff --git a/subprojects/persistent-cache/persistent-cache.gradle.kts b/subprojects/persistent-cache/persistent-cache.gradle.kts index 68706cee1a247..c12ab340c13b6 100644 --- a/subprojects/persistent-cache/persistent-cache.gradle.kts +++ b/subprojects/persistent-cache/persistent-cache.gradle.kts @@ -27,9 +27,7 @@ dependencies { api(project(":native")) api(project(":resources")) api(project(":logging")) - api(library("jcip")) - implementation(library("commons_collections")) implementation(library("commons_io")) implementation(library("commons_lang")) } diff --git a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/CrossBuildInMemoryCache.java b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/CrossBuildInMemoryCache.java index 94a12c4acded5..b220c1631736f 100644 --- a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/CrossBuildInMemoryCache.java +++ b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/CrossBuildInMemoryCache.java @@ -16,7 +16,7 @@ package org.gradle.cache.internal; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Transformer; import javax.annotation.Nullable; diff --git a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/CrossBuildInMemoryCacheFactory.java b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/CrossBuildInMemoryCacheFactory.java index f23b4eec64fbe..f296fa4d2f0da 100644 --- a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/CrossBuildInMemoryCacheFactory.java +++ b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/CrossBuildInMemoryCacheFactory.java @@ -16,7 +16,7 @@ package org.gradle.cache.internal; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; /** * A factory for {@link CrossBuildInMemoryCache} instances. diff --git a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/DefaultCacheAccess.java b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/DefaultCacheAccess.java index eddc374b7f6dd..593b2995bcaaa 100644 --- a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/DefaultCacheAccess.java +++ b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/DefaultCacheAccess.java @@ -17,7 +17,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.cache.AsyncCacheAccess; diff --git a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/FileContentCache.java b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/FileContentCache.java index 9d76c9dfd2da1..aefb4187dfb4c 100644 --- a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/FileContentCache.java +++ b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/FileContentCache.java @@ -16,7 +16,7 @@ package org.gradle.cache.internal; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import java.io.File; diff --git a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/btree/CachingBlockStore.java b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/btree/CachingBlockStore.java index 4bf4b3ef88018..ba815d35d0397 100644 --- a/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/btree/CachingBlockStore.java +++ b/subprojects/persistent-cache/src/main/java/org/gradle/cache/internal/btree/CachingBlockStore.java @@ -15,9 +15,9 @@ */ package org.gradle.cache.internal.btree; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import com.google.common.collect.ImmutableSet; -import org.apache.commons.collections.map.LRUMap; -import org.gradle.internal.Cast; import javax.annotation.Nullable; import java.util.Collection; @@ -28,7 +28,7 @@ public class CachingBlockStore implements BlockStore { private final BlockStore store; private final Map dirty = new LinkedHashMap(); - private final Map indexBlockCache = Cast.uncheckedCast(new LRUMap(100)); + private final Cache indexBlockCache = CacheBuilder.newBuilder().maximumSize(100).concurrencyLevel(1).build(); private final ImmutableSet> cacheableBlockTypes; public CachingBlockStore(BlockStore store, Collection> cacheableBlockTypes) { @@ -42,13 +42,13 @@ public void open(Runnable initAction, Factory factory) { public void close() { flush(); - indexBlockCache.clear(); + indexBlockCache.invalidateAll(); store.close(); } public void clear() { dirty.clear(); - indexBlockCache.clear(); + indexBlockCache.invalidateAll(); store.clear(); } @@ -69,7 +69,7 @@ public void attach(BlockPayload block) { public void remove(BlockPayload block) { dirty.remove(block.getPos()); if (isCacheable(block)) { - indexBlockCache.remove(block.getPos()); + indexBlockCache.invalidate(block.getPos()); } store.remove(block); } @@ -97,7 +97,7 @@ public T read(BlockPointer pos, Class payloadType) { @Nullable private T maybeGetFromCache(BlockPointer pos, Class payloadType) { if (cacheableBlockTypes.contains(payloadType)) { - return payloadType.cast(indexBlockCache.get(pos)); + return payloadType.cast(indexBlockCache.getIfPresent(pos)); } return null; } diff --git a/subprojects/platform-base/platform-base.gradle.kts b/subprojects/platform-base/platform-base.gradle.kts index 4cf0dac3da3fe..99ef2c5bde34e 100644 --- a/subprojects/platform-base/platform-base.gradle.kts +++ b/subprojects/platform-base/platform-base.gradle.kts @@ -9,7 +9,6 @@ dependencies { compile(project(":core")) compile(project(":dependencyManagement")) compile(project(":workers")) - compile(library("commons_collections")) compile(library("commons_lang")) } diff --git a/subprojects/platform-base/src/main/java/org/gradle/api/internal/resolve/LocalLibraryDependencyResolver.java b/subprojects/platform-base/src/main/java/org/gradle/api/internal/resolve/LocalLibraryDependencyResolver.java index 8eb86342ec118..001af7d81153b 100644 --- a/subprojects/platform-base/src/main/java/org/gradle/api/internal/resolve/LocalLibraryDependencyResolver.java +++ b/subprojects/platform-base/src/main/java/org/gradle/api/internal/resolve/LocalLibraryDependencyResolver.java @@ -30,6 +30,7 @@ import org.gradle.api.internal.artifacts.type.ArtifactTypeRegistry; import org.gradle.api.internal.attributes.ImmutableAttributes; import org.gradle.api.internal.component.ArtifactType; +import org.gradle.internal.Factory; import org.gradle.internal.component.external.model.MetadataSourcedComponentArtifacts; import org.gradle.internal.component.local.model.LocalComponentMetadata; import org.gradle.internal.component.local.model.PublishArtifactLocalArtifactMetadata; @@ -135,13 +136,25 @@ private void resolveLibraryAndChooseBinary(BuildableComponentIdResolveResult res return; } - Collection matchingVariants = chooseMatchingVariants(selectedLibrary, variant); + final Collection matchingVariants = chooseMatchingVariants(selectedLibrary, variant); if (matchingVariants.isEmpty()) { // no compatible variant found - Iterable values = selectedLibrary.getVariants(); - result.failed(new ModuleVersionResolveException(selector, errorMessageBuilder.noCompatibleVariantErrorMessage(libraryName, values))); + final Iterable values = selectedLibrary.getVariants(); + result.failed(new ModuleVersionResolveException(selector, new Factory() { + @Nullable + @Override + public String create() { + return errorMessageBuilder.noCompatibleVariantErrorMessage(libraryName, values); + } + })); } else if (matchingVariants.size() > 1) { - result.failed(new ModuleVersionResolveException(selector, errorMessageBuilder.multipleCompatibleVariantsErrorMessage(libraryName, matchingVariants))); + result.failed(new ModuleVersionResolveException(selector, new Factory() { + @Nullable + @Override + public String create() { + return errorMessageBuilder.multipleCompatibleVariantsErrorMessage(libraryName, matchingVariants); + } + })); } else { Binary selectedBinary = matchingVariants.iterator().next(); // TODO:Cedric This is not quite right. We assume that if we are asking for a specific binary, then we resolve to the assembly instead diff --git a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/BinarySourceTransformations.java b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/BinarySourceTransformations.java index 91603d3f17418..166e2155eefff 100644 --- a/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/BinarySourceTransformations.java +++ b/subprojects/platform-base/src/main/java/org/gradle/language/base/internal/model/BinarySourceTransformations.java @@ -18,7 +18,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; -import org.apache.commons.collections.comparators.BooleanComparator; +import com.google.common.primitives.Booleans; import org.gradle.api.Task; import org.gradle.api.tasks.TaskContainer; import org.gradle.internal.service.ServiceRegistry; @@ -30,7 +30,12 @@ import org.gradle.language.base.internal.registry.LanguageTransformContainer; import org.gradle.platform.base.internal.BinarySpecInternal; -import java.util.*; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; import static org.apache.commons.lang.StringUtils.capitalize; @@ -100,7 +105,7 @@ public void createTasksFor(BinarySpecInternal binary) { public int compare(LanguageTransform o1, LanguageTransform o2) { boolean joint1 = o1.getTransformTask() instanceof JointCompileTaskConfig; boolean joint2 = o2.getTransformTask() instanceof JointCompileTaskConfig; - return new BooleanComparator(true).compare(joint1, joint2); + return Booleans.trueFirst().compare(joint1, joint2); } }); return prioritized; diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/tasks/api/internal/ApiClassExtractorTest.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/tasks/api/internal/ApiClassExtractorTest.groovy index 9dda79d793458..aa04d25448040 100644 --- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/tasks/api/internal/ApiClassExtractorTest.groovy +++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/tasks/api/internal/ApiClassExtractorTest.groovy @@ -18,6 +18,7 @@ package org.gradle.jvm.tasks.api.internal import org.gradle.internal.classanalysis.AsmConstants import org.gradle.internal.reflect.JavaReflectionUtil +import org.junit.Assume import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.Label @@ -27,6 +28,8 @@ import spock.lang.Unroll import java.lang.reflect.Modifier +import static org.gradle.util.TestPrecondition.SUPPORTS_TARGETING_JAVA6 + class ApiClassExtractorTest extends ApiClassExtractorTestSupport { def "should not remove public method"() { @@ -284,6 +287,8 @@ class ApiClassExtractorTest extends ApiClassExtractorTestSupport { } void "target binary compatibility is maintained"() { + Assume.assumeFalse(target == "1.6" && !SUPPORTS_TARGETING_JAVA6.fulfilled) + given: def api = toApi(target, [A: 'public class A {}']) @@ -304,6 +309,7 @@ class ApiClassExtractorTest extends ApiClassExtractorTestSupport { target | expectedVersion '1.6' | 50 '1.7' | 51 + '1.8' | 52 } def "should not remove public field"() { diff --git a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/tasks/api/internal/ApiClassExtractorTestSupport.groovy b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/tasks/api/internal/ApiClassExtractorTestSupport.groovy index bed9de00908de..9b5f95865cf61 100644 --- a/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/tasks/api/internal/ApiClassExtractorTestSupport.groovy +++ b/subprojects/platform-jvm/src/test/groovy/org/gradle/jvm/tasks/api/internal/ApiClassExtractorTestSupport.groovy @@ -106,7 +106,7 @@ class ApiClassExtractorTestSupport extends Specification { public final TestNameTestDirectoryProvider temporaryFolder = new TestNameTestDirectoryProvider() protected ApiContainer toApi(Map sources) { - toApi('1.6', [], sources) + toApi('1.7', [], sources) } protected ApiContainer toApi(String targetVersion, Map sources) { @@ -114,7 +114,7 @@ class ApiClassExtractorTestSupport extends Specification { } protected ApiContainer toApi(List packages, Map sources) { - toApi('1.6', packages, sources) + toApi('1.7', packages, sources) } protected ApiContainer toApi(String targetVersion, List packages, Map sources) { diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativePlatformSamplesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativePlatformSamplesIntegrationTest.groovy index 2396cb8c5735b..6d8f08998aa8f 100644 --- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativePlatformSamplesIntegrationTest.groovy +++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/NativePlatformSamplesIntegrationTest.groovy @@ -25,6 +25,9 @@ import org.gradle.util.TestPrecondition import org.junit.Rule import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.GCC_COMPATIBLE +import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.SUPPORTS_32 +import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.SUPPORTS_32_AND_64 +import static org.junit.Assume.assumeTrue @Requires(TestPrecondition.CAN_INSTALL_EXECUTABLE) class NativePlatformSamplesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec { @@ -123,6 +126,7 @@ class NativePlatformSamplesIntegrationTest extends AbstractInstalledToolChainInt installation(flavors.dir.file("build/install/main/french")).exec().out == "Bonjour monde!\n" } + @RequiresInstalledToolChain(SUPPORTS_32_AND_64) def variants() { given: sample variants @@ -183,6 +187,8 @@ class NativePlatformSamplesIntegrationTest extends AbstractInstalledToolChainInt @RequiresInstalledToolChain(GCC_COMPATIBLE) def "target platforms"() { + assumeTrue(toolchainUnderTest.meets(SUPPORTS_32)) + given: sample targetPlatforms diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/platform/BinaryNativePlatformIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/platform/BinaryNativePlatformIntegrationTest.groovy index 5d4e35d839064..c11161bf71073 100755 --- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/platform/BinaryNativePlatformIntegrationTest.groovy +++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/platform/BinaryNativePlatformIntegrationTest.groovy @@ -19,6 +19,7 @@ import net.rubygrapefruit.platform.Native import net.rubygrapefruit.platform.SystemInfo import org.gradle.internal.os.OperatingSystem import org.gradle.nativeplatform.fixtures.AbstractInstalledToolChainIntegrationSpec +import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain import org.gradle.nativeplatform.fixtures.ToolChainRequirement import org.gradle.nativeplatform.fixtures.app.PlatformDetectingTestApp import org.gradle.nativeplatform.fixtures.binaryinfo.DumpbinBinaryInfo @@ -31,6 +32,9 @@ import org.gradle.util.TestPrecondition import spock.lang.Issue import spock.lang.Unroll +import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.SUPPORTS_32 +import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.SUPPORTS_32_AND_64 + @Requires(TestPrecondition.NOT_UNKNOWN_OS) class BinaryNativePlatformIntegrationTest extends AbstractInstalledToolChainIntegrationSpec { def testApp = new PlatformDetectingTestApp() @@ -74,6 +78,7 @@ model { binaryInfo(objectFileFor(file("src/main/cpp/main.cpp"), "build/objs/main/mainCpp")).arch.name == arch.name } + @RequiresInstalledToolChain(SUPPORTS_32) def "configure component for a single target platform"() { when: buildFile << """ @@ -131,6 +136,7 @@ model { executable("build/exe/main/main").exec().out == "${arch.altName} ${os.familyName}" * 2 } + @RequiresInstalledToolChain(SUPPORTS_32) def "library with matching platform is enforced by dependency resolution"() { given: testApp.executable.writeSources(file("src/exe")) @@ -202,6 +208,7 @@ model { executable("build/exe/exe/exe").exec().out == "${arch.altName} ${os.familyName}" * 2 } + @RequiresInstalledToolChain(SUPPORTS_32_AND_64) def "build binary for multiple target architectures"() { when: buildFile << """ @@ -249,6 +256,7 @@ model { } } + @RequiresInstalledToolChain(SUPPORTS_32) def "can configure binary for multiple target operating systems"() { String currentOs if (os.windows) { @@ -410,7 +418,7 @@ model { // Only the arch functionality is needed for this test, so fall back to the file utility if nothing else works. file.assertIsFile() if (os.macOsX) { - return new OtoolBinaryInfo(file) + return new OtoolBinaryInfo(file, toolchainUnderTest.runtimeEnv) } if (os.windows) { return DumpbinBinaryInfo.findVisualStudio() ? new DumpbinBinaryInfo(file) : new FileArchOnlyBinaryInfo(file) diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/GeneratedSourcesIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/GeneratedSourcesIntegrationTest.groovy index bd3c974b038f6..be2b15e5455ae 100755 --- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/GeneratedSourcesIntegrationTest.groovy +++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/sourceset/GeneratedSourcesIntegrationTest.groovy @@ -25,6 +25,7 @@ import org.gradle.nativeplatform.fixtures.app.CppHelloWorldApp import org.gradle.nativeplatform.fixtures.app.MixedLanguageHelloWorldApp import org.gradle.nativeplatform.fixtures.app.WindowsResourceHelloWorldApp +import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.SUPPORTS_32 import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.VISUALCPP // TODO: Test incremental class GeneratedSourcesIntegrationTest extends AbstractInstalledToolChainIntegrationSpec { @@ -250,6 +251,7 @@ model { executableBuilt(app) } + @RequiresInstalledToolChain(SUPPORTS_32) def "generator task produces assembler sources"() { given: def app = new MixedLanguageHelloWorldApp(AbstractInstalledToolChainIntegrationSpec.toolChain) diff --git a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/GccToolChainCustomisationIntegrationTest.groovy b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/GccToolChainCustomisationIntegrationTest.groovy index 574cf1ca2263e..a0cf3829ccba5 100755 --- a/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/GccToolChainCustomisationIntegrationTest.groovy +++ b/subprojects/platform-native/src/integTest/groovy/org/gradle/nativeplatform/toolchain/GccToolChainCustomisationIntegrationTest.groovy @@ -25,6 +25,7 @@ import org.gradle.util.Requires import org.gradle.util.TestPrecondition import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.GCC_COMPATIBLE +import static org.gradle.nativeplatform.fixtures.ToolChainRequirement.SUPPORTS_32 @RequiresInstalledToolChain(GCC_COMPATIBLE) class GccToolChainCustomisationIntegrationTest extends AbstractInstalledToolChainIntegrationSpec { @@ -53,6 +54,7 @@ model { helloWorldApp.library.writeSources(file("src/hello")) } + @RequiresInstalledToolChain(SUPPORTS_32) def "can configure platform specific args"() { when: buildFile << """ diff --git a/subprojects/platform-native/src/main/java/org/gradle/language/swift/SwiftVersion.java b/subprojects/platform-native/src/main/java/org/gradle/language/swift/SwiftVersion.java index d34624964757b..32a30976847ba 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/language/swift/SwiftVersion.java +++ b/subprojects/platform-native/src/main/java/org/gradle/language/swift/SwiftVersion.java @@ -25,7 +25,14 @@ */ @Incubating public enum SwiftVersion { - SWIFT3(3), SWIFT4(4); + SWIFT3(3), SWIFT4(4), + + /** + * Swift 5 major version. + * + * @since 5.4 + */ + SWIFT5(5); private final int version; diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChain.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChain.java index 50abf19fdad33..43c68be40cc01 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChain.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChain.java @@ -15,7 +15,6 @@ */ package org.gradle.nativeplatform.toolchain.internal.gcc; -import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import org.gradle.api.Action; import org.gradle.api.NonNullApi; @@ -228,7 +227,7 @@ protected void initTools(DefaultGccPlatformToolChain platformToolChain, ToolChai for (GccCommandLineToolConfigurationInternal tool : platformToolChain.getCompilers()) { CommandLineToolSearchResult compiler = locate(tool); if (compiler.isAvailable()) { - SearchResult gccMetadata = getMetaDataProvider().getCompilerMetaData(compiler.getTool(), platformToolChain.getCompilerProbeArgs(), toolSearchPath.getPath()); + SearchResult gccMetadata = getMetaDataProvider().getCompilerMetaData(toolSearchPath.getPath(), spec -> spec.executable(compiler.getTool()).args(platformToolChain.getCompilerProbeArgs())); availability.mustBeAvailable(gccMetadata); if (!gccMetadata.isAvailable()) { return; @@ -350,8 +349,11 @@ public CompilerMetaDataProviderWithDefaultArgs(List compilerProbeArgs, C } @Override - public SearchResult getCompilerMetaData(File binary, List additionalArgs, List searchPath) { - return delegate.getCompilerMetaData(binary, ImmutableList.builder().addAll(compilerProbeArgs).addAll(additionalArgs).build(), searchPath); + public SearchResult getCompilerMetaData(List searchPath, Action configureAction) { + return delegate.getCompilerMetaData(searchPath, execSpec -> { + execSpec.args(compilerProbeArgs); + configureAction.execute(execSpec); + }); } @Override diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProvider.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProvider.java index 489241cf33bf1..d614505b9ff40 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProvider.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProvider.java @@ -228,7 +228,7 @@ private SearchResult getGccMetadata(ToolType compilerType) { CommandLineToolSearchResult searchResult = toolSearchPath.locate(compiler.getToolType(), compiler.getExecutable()); String language = LANGUAGE_FOR_COMPILER.get(compilerType); List languageArgs = language == null ? Collections.emptyList() : ImmutableList.of("-x", language); - return metadataProvider.getCompilerMetaData(searchResult.getTool(), languageArgs, toolSearchPath.getPath()); + return metadataProvider.getCompilerMetaData(toolSearchPath.getPath(), spec -> spec.executable(searchResult.getTool()).args(languageArgs)); } @Override diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/AbstractMetadataProvider.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/AbstractMetadataProvider.java index b8a2643f84c69..0e34071ee756f 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/AbstractMetadataProvider.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/AbstractMetadataProvider.java @@ -18,6 +18,7 @@ import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; +import org.gradle.api.Action; import org.gradle.internal.Pair; import org.gradle.internal.io.StreamByteBuffer; import org.gradle.platform.base.internal.toolchain.ComponentFound; @@ -28,7 +29,10 @@ import org.gradle.process.internal.ExecActionFactory; import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public abstract class AbstractMetadataProvider implements CompilerMetaDataProvider { private final ExecActionFactory execActionFactory; @@ -38,16 +42,19 @@ public AbstractMetadataProvider(ExecActionFactory execActionFactory) { } @Override - public SearchResult getCompilerMetaData(File binary, List additionalArgs, List path) { - List allArgs = ImmutableList.builder().addAll(additionalArgs).addAll(compilerArgs()).build(); - Pair transform = runCompiler(binary, allArgs); + public SearchResult getCompilerMetaData(List path, Action configureAction) { + DefaultCompilerExecSpec execSpec = new DefaultCompilerExecSpec(); + configureAction.execute(execSpec); + + List allArgs = ImmutableList.builder().addAll(execSpec.args).addAll(compilerArgs()).build(); + Pair transform = runCompiler(execSpec.executable, allArgs, execSpec.environments); if (transform == null) { - return new ComponentNotFound(String.format("Could not determine %s metadata: failed to execute %s %s.", getCompilerType().getDescription(), binary.getName(), Joiner.on(' ').join(allArgs))); + return new ComponentNotFound(String.format("Could not determine %s metadata: failed to execute %s %s.", getCompilerType().getDescription(), execSpec.executable.getName(), Joiner.on(' ').join(allArgs))); } String output = transform.getLeft(); String error = transform.getRight(); try { - return new ComponentFound(parseCompilerOutput(output, error, binary, path)); + return new ComponentFound(parseCompilerOutput(output, error, execSpec.executable, path)); } catch (BrokenResultException e) { return new ComponentNotFound(e.getMessage()); } @@ -59,11 +66,12 @@ protected ExecActionFactory getExecActionFactory() { protected abstract T parseCompilerOutput(String output, String error, File binary, List path) throws BrokenResultException; - private Pair runCompiler(File gccBinary, List args) { + private Pair runCompiler(File gccBinary, List args, Map environmentVariables) { ExecAction exec = execActionFactory.newExecAction(); exec.executable(gccBinary.getAbsolutePath()); exec.setWorkingDir(gccBinary.getParentFile()); exec.args(args); + exec.environment(environmentVariables); StreamByteBuffer buffer = new StreamByteBuffer(); StreamByteBuffer errorBuffer = new StreamByteBuffer(); exec.setStandardOutput(buffer.getOutputStream()); @@ -87,4 +95,28 @@ public BrokenResultException(String message) { } } + public static class DefaultCompilerExecSpec implements CompilerExecSpec { + public final Map environments = new HashMap<>(); + public final List args = new ArrayList<>(); + public File executable; + + @Override + public CompilerExecSpec environment(String key, String value) { + environments.put(key, value); + return this; + } + + @Override + public CompilerExecSpec executable(File executable) { + this.executable = executable; + return this; + } + + @Override + public CompilerExecSpec args(Iterable args) { + this.args.addAll(ImmutableList.copyOf(args)); + return this; + } + } + } diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProvider.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProvider.java index 6cd90ddb20447..80079719bcf89 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProvider.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProvider.java @@ -16,6 +16,7 @@ package org.gradle.nativeplatform.toolchain.internal.metadata; +import org.gradle.api.Action; import org.gradle.platform.base.internal.toolchain.SearchResult; import java.io.File; @@ -23,8 +24,13 @@ public interface CompilerMetaDataProvider { - SearchResult getCompilerMetaData(File binary, List additionalArgs, List searchPath); + SearchResult getCompilerMetaData(List searchPath, Action configureAction); CompilerType getCompilerType(); + interface CompilerExecSpec { + CompilerExecSpec environment(String key, String value); + CompilerExecSpec executable(File executable); + CompilerExecSpec args(Iterable args); + } } diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProviderFactory.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProviderFactory.java index ea77e73b42e03..2584fc7ac7b5b 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProviderFactory.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProviderFactory.java @@ -16,6 +16,7 @@ package org.gradle.nativeplatform.toolchain.internal.metadata; +import org.gradle.api.Action; import org.gradle.nativeplatform.toolchain.internal.gcc.metadata.GccMetadata; import org.gradle.nativeplatform.toolchain.internal.gcc.metadata.GccMetadataProvider; import org.gradle.nativeplatform.toolchain.internal.swift.metadata.SwiftcMetadata; @@ -60,11 +61,14 @@ private CachingCompilerMetaDataProvider(CompilerMetaDataProvider delegate) { } @Override - public SearchResult getCompilerMetaData(File binary, List additionalArgs, List path) { - Key key = new Key(binary, additionalArgs, path); + public SearchResult getCompilerMetaData(List path, Action configureAction) { + AbstractMetadataProvider.DefaultCompilerExecSpec execSpec = new AbstractMetadataProvider.DefaultCompilerExecSpec(); + configureAction.execute(execSpec); + + Key key = new Key(execSpec.executable, execSpec.args, path, execSpec.environments); SearchResult result = resultMap.get(key); if (result == null) { - result = delegate.getCompilerMetaData(binary, additionalArgs, path); + result = delegate.getCompilerMetaData(path, configureAction); resultMap.put(key, result); } return result; @@ -80,22 +84,24 @@ private static class Key { final File gccBinary; final List args; final List path; + private final Map environmentVariables; - private Key(File gccBinary, List args, List path) { + private Key(File gccBinary, List args, List path, Map environmentVariables) { this.gccBinary = gccBinary; this.args = args; this.path = path; + this.environmentVariables = environmentVariables; } @Override public boolean equals(Object obj) { Key other = (Key) obj; - return other.gccBinary.equals(gccBinary) && other.args.equals(args) && other.path.equals(path); + return other.gccBinary.equals(gccBinary) && other.args.equals(args) && other.path.equals(path) && other.environmentVariables.equals(environmentVariables); } @Override public int hashCode() { - return gccBinary.hashCode() ^ args.hashCode() ^ path.hashCode(); + return gccBinary.hashCode() ^ args.hashCode() ^ path.hashCode() ^ environmentVariables.hashCode(); } } } diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftCompiler.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftCompiler.java index 3412988dbcc32..0ad296575a6d3 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftCompiler.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftCompiler.java @@ -30,6 +30,7 @@ import org.gradle.internal.operations.BuildOperationQueue; import org.gradle.internal.os.OperatingSystem; import org.gradle.internal.work.WorkerLeaseService; +import org.gradle.language.swift.SwiftVersion; import org.gradle.nativeplatform.internal.CompilerOutputFileNamingSchemeFactory; import org.gradle.nativeplatform.toolchain.internal.AbstractCompiler; import org.gradle.nativeplatform.toolchain.internal.ArgsTransformer; @@ -71,7 +72,7 @@ protected void addOptionsFileArgs(List args, File tempDir) { @Override public WorkResult execute(SwiftCompileSpec spec) { - if (swiftCompilerVersion.getMajor() < spec.getSourceCompatibility().getVersion()) { + if (swiftCompilerVersion.getMajor() < spec.getSourceCompatibility().getVersion() || (swiftCompilerVersion.getMajor() >= 5 && spec.getSourceCompatibility().equals(SwiftVersion.SWIFT3))) { throw new IllegalArgumentException(String.format("Swift compiler version '%s' doesn't support Swift language version '%d'", swiftCompilerVersion.toString(), spec.getSourceCompatibility().getVersion())); } return super.execute(spec); diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftPlatformToolProvider.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftPlatformToolProvider.java index e650670da636f..921d86e41e000 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftPlatformToolProvider.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftPlatformToolProvider.java @@ -129,6 +129,11 @@ private CommandLineToolInvocationWorker commandLineTool(ToolType key, String exe private CommandLineToolContext context(CommandLineToolConfigurationInternal toolConfiguration) { MutableCommandLineToolContext baseInvocation = new DefaultMutableCommandLineToolContext(); baseInvocation.setArgAction(toolConfiguration.getArgAction()); + + String developerDir = System.getenv("DEVELOPER_DIR"); + if (developerDir != null) { + baseInvocation.addEnvironmentVar("DEVELOPER_DIR", developerDir); + } return baseInvocation; } diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftcToolChain.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftcToolChain.java index 82c232c83e90d..c389984530f11 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftcToolChain.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/swift/SwiftcToolChain.java @@ -44,7 +44,6 @@ import org.gradle.process.internal.ExecActionFactory; import java.io.File; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -101,7 +100,7 @@ private PlatformToolProvider createPlatformToolProvider(NativePlatformInternal t if (!result.isAvailable()) { return new UnavailablePlatformToolProvider(targetPlatform.getOperatingSystem(), result); } - SearchResult swiftcMetaData = compilerMetaDataProvider.getCompilerMetaData(compiler.getTool(), Collections.emptyList(), toolSearchPath.getPath()); + SearchResult swiftcMetaData = compilerMetaDataProvider.getCompilerMetaData(toolSearchPath.getPath(), spec -> spec.executable(compiler.getTool())); result.mustBeAvailable(swiftcMetaData); if (!result.isAvailable()) { return new UnavailablePlatformToolProvider(targetPlatform.getOperatingSystem(), result); diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/tools/CommandLineToolConfigurationInternal.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/tools/CommandLineToolConfigurationInternal.java index 9faa367cdd76d..87d888ed9898d 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/tools/CommandLineToolConfigurationInternal.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/tools/CommandLineToolConfigurationInternal.java @@ -21,7 +21,7 @@ import java.util.List; -public interface CommandLineToolConfigurationInternal extends CommandLineToolConfiguration{ +public interface CommandLineToolConfigurationInternal extends CommandLineToolConfiguration { public Action> getArgAction(); } diff --git a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/xcode/AbstractLocator.java b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/xcode/AbstractLocator.java index 4caeea05a1397..55ff6ab380e46 100644 --- a/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/xcode/AbstractLocator.java +++ b/subprojects/platform-native/src/main/java/org/gradle/nativeplatform/toolchain/internal/xcode/AbstractLocator.java @@ -41,6 +41,11 @@ public File find() { execAction.executable("xcrun"); execAction.workingDir(System.getProperty("user.dir")); execAction.args(getXcrunFlags()); + + String developerDir = System.getenv("DEVELOPER_DIR"); + if (developerDir != null) { + execAction.environment("DEVELOPER_DIR", developerDir); + } execAction.setStandardOutput(outputStream); execAction.execute().assertNormalExitValue(); diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPluginTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPluginTest.groovy index 59df141cd67e4..c7344d3be5520 100644 --- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPluginTest.groovy +++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/plugins/NativeComponentModelPluginTest.groovy @@ -47,6 +47,7 @@ import org.gradle.util.TestUtil import spock.lang.Issue import static org.gradle.model.internal.type.ModelTypes.modelMap +import static org.gradle.util.CollectionUtils.single class NativeComponentModelPluginTest extends AbstractProjectBuilderSpec { def registry @@ -120,8 +121,8 @@ class NativeComponentModelPluginTest extends AbstractProjectBuilderSpec { } then: - one(binaries.withType(NativeExecutableBinarySpec)).flavor.name == DefaultFlavor.DEFAULT - one(binaries.withType(SharedLibraryBinarySpec)).flavor.name == DefaultFlavor.DEFAULT + single(binaries.withType(NativeExecutableBinarySpec)).flavor.name == DefaultFlavor.DEFAULT + single(binaries.withType(SharedLibraryBinarySpec)).flavor.name == DefaultFlavor.DEFAULT } def "behaves correctly for defaults when domain is explicitly configured"() { @@ -133,10 +134,10 @@ class NativeComponentModelPluginTest extends AbstractProjectBuilderSpec { .mutate(FlavorContainer) { it.add named(Flavor, "flavor1") } then: - one(toolChains).name == 'tc' + single(toolChains).name == 'tc' platforms.size() == 1 - one(buildTypes).name == 'bt' - one(flavors).name == 'flavor1' + single(buildTypes).name == 'bt' + single(flavors).name == 'flavor1' } def "creates binaries for executable"() { @@ -157,8 +158,8 @@ class NativeComponentModelPluginTest extends AbstractProjectBuilderSpec { } then: - NativeExecutableSpec executable = one(components.values()) as NativeExecutableSpec - NativeExecutableBinarySpec executableBinary = one(binaries) as NativeExecutableBinarySpec + NativeExecutableSpec executable = single(components.values()) as NativeExecutableSpec + NativeExecutableBinarySpec executableBinary = single(binaries) as NativeExecutableBinarySpec with(executableBinary) { name == 'executable' component == executable @@ -190,7 +191,7 @@ class NativeComponentModelPluginTest extends AbstractProjectBuilderSpec { } then: - NativeLibrarySpec library = one(components.values()) as NativeLibrarySpec + NativeLibrarySpec library = single(components.values()) as NativeLibrarySpec SharedLibraryBinarySpec sharedLibraryBinary = binaries.testSharedLibrary as SharedLibraryBinarySpec with(sharedLibraryBinary) { name == 'sharedLibrary' @@ -247,14 +248,6 @@ class NativeComponentModelPluginTest extends AbstractProjectBuilderSpec { } } - static T one(Iterable iterable) { - def iterator = iterable.iterator() - assert iterator.hasNext() - def item = iterator.next() - assert !iterator.hasNext() - return item - } - public T named(Class type, def name) { Stub(type) { getName() >> name diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChainTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChainTest.groovy index d1b842733633d..43a267c4eaf9c 100644 --- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChainTest.groovy +++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/AbstractGccCompatibleToolChainTest.groovy @@ -134,7 +134,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { and: toolSearchPath.locate(ToolType.CPP_COMPILER, "g++") >> compilerMissing toolSearchPath.locate(_, _) >> tool - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler expect: def platformToolChain = toolChain.select(NativeLanguage.CPP, platform) @@ -154,7 +154,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { and: toolSearchPath.locate(_, _) >> tool - metaDataProvider.getCompilerMetaData(_, _, _) >> wrongCompiler + metaDataProvider.getCompilerMetaData(_, _) >> wrongCompiler expect: def platformToolChain = toolChain.select(language, platform) @@ -173,7 +173,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { and: toolSearchPath.locate(toolType, _) >> tool toolSearchPath.locate(_, _) >> missing - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler expect: toolChain.select(platform).available @@ -190,7 +190,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { and: toolSearchPath.locate(ToolType.CPP_COMPILER, _) >> tool toolSearchPath.locate(_, _) >> missing - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler expect: toolChain.select(platform).available @@ -203,7 +203,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { and: toolSearchPath.locate(_, _) >> tool - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler expect: toolChain.select(platform).available @@ -229,7 +229,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { platform2.operatingSystem >> dummyOs toolSearchPath.locate(_, _) >> tool - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler given: int platformActionApplied = 0 @@ -262,7 +262,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { platform.getOperatingSystem() >> dummyOs platform.getArchitecture() >> dummyArch toolChain.eachPlatform(action) - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler when: toolChain.select(platform) @@ -287,7 +287,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { platform.operatingSystem >> dummyOs platform.architecture >> Architectures.forInput(arch) toolChain.eachPlatform(action) - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler when: toolChain.select(platform) @@ -316,7 +316,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { toolSearchPath.locate(_, _) >> tool platform.operatingSystem >> new DefaultOperatingSystem("osx", OperatingSystem.MAC_OS) platform.architecture >> new DefaultArchitecture(arch) - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler toolChain.target(platform.name) toolChain.eachPlatform(action) @@ -348,7 +348,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { def platformConfig2 = Mock(Action) toolSearchPath.locate(_, _) >> tool - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler toolChain.target("platform1", platformConfig1) toolChain.target("platform2", platformConfig2) @@ -370,7 +370,7 @@ class AbstractGccCompatibleToolChainTest extends Specification { when: toolSearchPath.locate(_, _) >> tool - metaDataProvider.getCompilerMetaData(_, _, _) >> correctCompiler + metaDataProvider.getCompilerMetaData(_, _) >> correctCompiler and: toolChain.target(platform.getName(), new Action() { diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProviderTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProviderTest.groovy index 8bb31946d90e9..57f6be4686cdd 100644 --- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProviderTest.groovy +++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/GccPlatformToolProviderTest.groovy @@ -58,8 +58,8 @@ class GccPlatformToolProviderTest extends Specification { then: result == libs - 1 * metaDataProvider.getCompilerMetaData(_, _, _) >> { - assert arguments[1] == args + 1 * metaDataProvider.getCompilerMetaData(_, _) >> { + arguments[1].execute(assertingCompilerExecSpecArguments(args)) new ComponentFound(metaData) } 1 * toolRegistry.getTool(toolType) >> new DefaultGccCommandLineToolConfiguration(toolType, 'exe') @@ -81,8 +81,8 @@ class GccPlatformToolProviderTest extends Specification { platformToolProvider.getCompilerMetadata(toolType) then: - 1 * metaDataProvider.getCompilerMetaData(_, _, _) >> { - assert arguments[1] == args + 1 * metaDataProvider.getCompilerMetaData(_, _) >> { + arguments[1].execute(assertingCompilerExecSpecArguments(args)) Mock(SearchResult) } 1 * toolRegistry.getTool(toolType) >> new DefaultGccCommandLineToolConfiguration(toolType, 'exe') @@ -96,4 +96,24 @@ class GccPlatformToolProviderTest extends Specification { ToolType.OBJECTIVECPP_COMPILER | ['-x', 'objective-c++'] ToolType.ASSEMBLER | [] } + + CompilerMetaDataProvider.CompilerExecSpec assertingCompilerExecSpecArguments(Iterable expectedArgs) { + return new CompilerMetaDataProvider.CompilerExecSpec() { + @Override + CompilerMetaDataProvider.CompilerExecSpec environment(String key, String value) { + return this + } + + @Override + CompilerMetaDataProvider.CompilerExecSpec executable(File executable) { + return this + } + + @Override + CompilerMetaDataProvider.CompilerExecSpec args(Iterable args) { + assert args == expectedArgs + return this + } + } + } } diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/metadata/GccMetadataProviderTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/metadata/GccMetadataProviderTest.groovy index 7a551e55d7797..862ed8396c295 100644 --- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/metadata/GccMetadataProviderTest.groovy +++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/gcc/metadata/GccMetadataProviderTest.groovy @@ -247,7 +247,7 @@ End of search list. def binary = new File("g++") when: - def result = metadataProvider.getCompilerMetaData(binary, [], []) + def result = metadataProvider.getCompilerMetaData([]) { it.executable(binary) } then: 1 * execActionFactory.newExecAction() >> action @@ -354,7 +354,7 @@ End of search list. mapsPath(cygpath, '/usr/include', 'C:\\cygwin\\usr\\include') mapsPath(cygpath, '/usr/local/include', 'C:\\cygwin\\usr\\local\\include') def provider = new GccMetadataProvider(execActionFactory, GCC) - def result = provider.getCompilerMetaData(new File("gcc"), [], [binDir]) + def result = provider.getCompilerMetaData([binDir]) { it.executable(new File("gcc")) } result.component.systemIncludes*.path == mapped } @@ -369,7 +369,7 @@ End of search list. SearchResult output(String output, String error, GccCompilerType compilerType = GCC, List path = []) { runsCompiler(output, error) def provider = new GccMetadataProvider(execActionFactory, compilerType) - provider.getCompilerMetaData(new File("g++"), [], path) + provider.getCompilerMetaData(path) { it.executable(new File("g++")) } } void runsCompiler(String output, String error) { diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProviderFactoryTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProviderFactoryTest.groovy index 8b1b6f4975599..22364a382a975 100644 --- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProviderFactoryTest.groovy +++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/metadata/CompilerMetaDataProviderFactoryTest.groovy @@ -33,13 +33,13 @@ class CompilerMetaDataProviderFactoryTest extends Specification { def "caches result of actual #compiler metadata provider"() { def binary = new File("any") when: - def metadata = metadataProvider(compiler).getCompilerMetaData(binary, [], []) + def metadata = metadataProvider(compiler).getCompilerMetaData([]) { it.executable(binary) } then: interaction compilerShouldBeExecuted when: - def newMetadata = metadataProvider(compiler).getCompilerMetaData(binary, [], []) + def newMetadata = metadataProvider(compiler).getCompilerMetaData([]) { it.executable(binary) } then: 0 * _ @@ -54,20 +54,20 @@ class CompilerMetaDataProviderFactoryTest extends Specification { def firstBinary = new File("first") def secondBinary = new File("second") when: - def firstMetadata = metadataProvider(compiler).getCompilerMetaData(firstBinary, [], []) + def firstMetadata = metadataProvider(compiler).getCompilerMetaData([]) { it.executable(firstBinary) } then: interaction compilerShouldBeExecuted when: - def secondMetadata = metadataProvider(compiler).getCompilerMetaData(secondBinary, [], []) + def secondMetadata = metadataProvider(compiler).getCompilerMetaData([]) { it.executable(secondBinary) } then: interaction compilerShouldBeExecuted firstMetadata != secondMetadata when: - def firstMetadataAgain = metadataProvider(compiler).getCompilerMetaData(firstBinary, [], []) + def firstMetadataAgain = metadataProvider(compiler).getCompilerMetaData([]) { it.executable(firstBinary) } then: 0 * _ @@ -83,20 +83,20 @@ class CompilerMetaDataProviderFactoryTest extends Specification { def firstArgs = ["-m32"] def secondArgs = ["-m64"] when: - def firstMetadata = metadataProvider(compiler).getCompilerMetaData(binary, firstArgs, []) + def firstMetadata = metadataProvider(compiler).getCompilerMetaData([]) { it.executable(binary).args(firstArgs) } then: interaction compilerShouldBeExecuted when: - def secondMetadata = metadataProvider(compiler).getCompilerMetaData(binary, secondArgs, []) + def secondMetadata = metadataProvider(compiler).getCompilerMetaData([]) { it.executable(binary).args(secondArgs) } then: interaction compilerShouldBeExecuted firstMetadata != secondMetadata when: - def firstMetadataAgain = metadataProvider(compiler).getCompilerMetaData(binary, firstArgs, []) + def firstMetadataAgain = metadataProvider(compiler).getCompilerMetaData([]) { it.executable(binary).args(firstArgs) } then: 0 * _ @@ -112,20 +112,20 @@ class CompilerMetaDataProviderFactoryTest extends Specification { def firstPath = [] def secondPath = [new File("/usr/local/bin")] when: - def firstMetadata = metadataProvider(compiler).getCompilerMetaData(binary, [], firstPath) + def firstMetadata = metadataProvider(compiler).getCompilerMetaData(firstPath) { it.executable(binary) } then: interaction compilerShouldBeExecuted when: - def secondMetadata = metadataProvider(compiler).getCompilerMetaData(binary, [], secondPath) + def secondMetadata = metadataProvider(compiler).getCompilerMetaData(secondPath) { it.executable(binary) } then: interaction compilerShouldBeExecuted firstMetadata != secondMetadata when: - def firstMetadataAgain = metadataProvider(compiler).getCompilerMetaData(binary, [], firstPath) + def firstMetadataAgain = metadataProvider(compiler).getCompilerMetaData(firstPath) { it.executable(binary) } then: 0 * _ diff --git a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/swift/metadata/SwiftcMetadataProviderTest.groovy b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/swift/metadata/SwiftcMetadataProviderTest.groovy index 58837acc78348..f347f56e25a30 100644 --- a/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/swift/metadata/SwiftcMetadataProviderTest.groovy +++ b/subprojects/platform-native/src/test/groovy/org/gradle/nativeplatform/toolchain/internal/swift/metadata/SwiftcMetadataProviderTest.groovy @@ -75,7 +75,7 @@ Target: x86_64-unknown-linux-gnu def binary = new File("swiftc") when: - def result = metadataProvider.getCompilerMetaData(binary, [], []) + def result = metadataProvider.getCompilerMetaData([]) { it.executable(binary) } then: 1 * execActionFactory.newExecAction() >> action @@ -99,7 +99,7 @@ Target: x86_64-unknown-linux-gnu 1 * action.setStandardOutput(_) >> { OutputStream outstr -> outstr << output; action } 1 * action.execute() >> result def provider = new SwiftcMetadataProvider(execActionFactory) - provider.getCompilerMetaData(new File("swiftc"), [], []) + provider.getCompilerMetaData([]) { it.executable(new File("swiftc")) } } } diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/AbstractInstalledToolChainIntegrationSpec.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/AbstractInstalledToolChainIntegrationSpec.groovy index 29cafa580af39..6d98a7a5476fb 100755 --- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/AbstractInstalledToolChainIntegrationSpec.groovy +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/AbstractInstalledToolChainIntegrationSpec.groovy @@ -54,6 +54,7 @@ abstract class AbstractInstalledToolChainIntegrationSpec extends AbstractIntegra """ executer.beforeExecute({ usingInitScript(initScript) + toolChain.configureExecuter(it) }) } diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/AvailableToolChains.java b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/AvailableToolChains.java index 499d8e05b2e4c..fc716722097b6 100755 --- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/AvailableToolChains.java +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/AvailableToolChains.java @@ -17,11 +17,13 @@ package org.gradle.nativeplatform.fixtures; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import org.gradle.api.internal.file.TestFiles; import org.gradle.api.specs.Spec; import org.gradle.integtests.fixtures.AbstractContextualMultiVersionSpecRunner; +import org.gradle.integtests.fixtures.executer.GradleExecuter; import org.gradle.internal.nativeintegration.ProcessEnvironment; import org.gradle.internal.os.OperatingSystem; import org.gradle.nativeplatform.fixtures.msvcpp.VisualStudioLocatorTestFixture; @@ -62,7 +64,7 @@ import static org.gradle.nativeplatform.fixtures.msvcpp.VisualStudioVersion.VISUALSTUDIO_2017; public class AvailableToolChains { - private static final Comparator LATEST_FIRST = Collections.reverseOrder(new Comparator() { + private static final Comparator LATEST_RELEASED_FIRST = Collections.reverseOrder(new Comparator() { @Override public int compare(ToolChainCandidate toolchain1, ToolChainCandidate toolchain2) { return toolchain1.getVersion().compareTo(toolchain2.getVersion()); @@ -133,7 +135,7 @@ static private List findClangs(boolean mustFind) { if (!clangCandidates.isEmpty()) { File firstInPath = clangCandidates.iterator().next(); for (File candidate : clangCandidates) { - SearchResult version = versionDeterminer.getCompilerMetaData(candidate, Collections.emptyList(), Collections.emptyList()); + SearchResult version = versionDeterminer.getCompilerMetaData(Collections.emptyList(), spec -> spec.executable(candidate)); if (version.isAvailable()) { InstalledClang clang = new InstalledClang(version.getComponent().getVersion()); if (!candidate.equals(firstInPath)) { @@ -149,7 +151,7 @@ static private List findClangs(boolean mustFind) { toolChains.add(new UnavailableToolChain(ToolFamily.CLANG)); } - toolChains.sort(LATEST_FIRST); + toolChains.sort(LATEST_RELEASED_FIRST); return toolChains; } @@ -183,7 +185,7 @@ static private List findVisualCpps() { toolChains.add(new UnavailableToolChain(ToolFamily.VISUAL_CPP)); } - toolChains.sort(LATEST_FIRST); + toolChains.sort(LATEST_RELEASED_FIRST); return toolChains; } @@ -225,7 +227,7 @@ static private List findGccs(boolean mustFind) { if (!gppCandidates.isEmpty()) { File firstInPath = gppCandidates.iterator().next(); for (File candidate : gppCandidates) { - SearchResult version = versionDeterminer.getCompilerMetaData(candidate, Collections.emptyList(), Collections.emptyList()); + SearchResult version = versionDeterminer.getCompilerMetaData(Collections.emptyList(), spec -> spec.executable(candidate)); if (version.isAvailable()) { InstalledGcc gcc = new InstalledGcc(ToolFamily.GCC, version.getComponent().getVersion()); if (!candidate.equals(firstInPath)) { @@ -241,7 +243,7 @@ static private List findGccs(boolean mustFind) { toolChains.add(new UnavailableToolChain(ToolFamily.GCC)); } - toolChains.sort(LATEST_FIRST); + toolChains.sort(LATEST_RELEASED_FIRST); return toolChains; } @@ -253,25 +255,37 @@ static List findSwiftcs() { // On Linux, we assume swift is installed into /opt/swift File rootSwiftInstall = new File("/opt/swift"); - File[] candidates = GUtil.elvis(rootSwiftInstall.listFiles(new FileFilter() { + File[] swiftCandidates = GUtil.elvis(rootSwiftInstall.listFiles(new FileFilter() { @Override public boolean accept(File swiftInstall) { return swiftInstall.isDirectory() && !swiftInstall.getName().equals("latest"); } }), new File[0]); - for (File swiftInstall : candidates) { + for (File swiftInstall : swiftCandidates) { File swiftc = new File(swiftInstall, "/usr/bin/swiftc"); - SearchResult version = versionDeterminer.getCompilerMetaData(swiftc, Collections.emptyList(), Collections.emptyList()); + SearchResult version = versionDeterminer.getCompilerMetaData(Collections.emptyList(), spec -> spec.executable(swiftc)); if (version.isAvailable()) { File binDir = swiftc.getParentFile(); toolChains.add(new InstalledSwiftc(binDir, version.getComponent().getVersion()).inPath(binDir, new File("/usr/bin"))); } } + // On macOS, we assume co-located Xcode is installed into /opt/xcode + File rootXcodeInstall = new File("/opt/xcode"); + File[] xcodeCandidates = GUtil.elvis(rootXcodeInstall.listFiles(xcodeInstall -> xcodeInstall.isDirectory()), new File[0]); + for (File xcodeInstall : xcodeCandidates) { + File swiftc = new File("/usr/bin/swiftc"); + SearchResult version = versionDeterminer.getCompilerMetaData(Collections.emptyList(), spec -> spec.executable(swiftc).environment("DEVELOPER_DIR", xcodeInstall.getAbsolutePath())); + if (version.isAvailable()) { + File binDir = swiftc.getParentFile(); + toolChains.add(new InstalledXcode(xcodeInstall, version.getComponent().getVersion()).inPath(binDir, new File("/usr/bin"))); + } + } + List swiftcCandidates = OperatingSystem.current().findAllInPath("swiftc"); for (File candidate : swiftcCandidates) { - SearchResult version = versionDeterminer.getCompilerMetaData(candidate, Collections.emptyList(), Collections.emptyList()); + SearchResult version = versionDeterminer.getCompilerMetaData(Collections.emptyList(), spec -> spec.executable(candidate)); if (version.isAvailable()) { File binDir = candidate.getParentFile(); InstalledSwiftc swiftc = new InstalledSwiftc(binDir, version.getComponent().getVersion()); @@ -283,7 +297,7 @@ public boolean accept(File swiftInstall) { if (toolChains.isEmpty()) { toolChains.add(new UnavailableToolChain(ToolFamily.SWIFTC)); } else { - toolChains.sort(LATEST_FIRST); + toolChains.sort(LATEST_RELEASED_FIRST); } return toolChains; @@ -464,6 +478,10 @@ public boolean matches(String criteria) { public String platformSpecificToolChainConfiguration() { return ""; } + + public void configureExecuter(GradleExecuter executer) { + // Toolchains should be using default configuration + } } public static abstract class GccCompatibleToolChain extends InstalledToolChain { @@ -697,7 +715,63 @@ public String getUnitTestPlatform() { @Override public boolean meets(ToolChainRequirement requirement) { - return requirement == ToolChainRequirement.SWIFTC || (requirement == ToolChainRequirement.SWIFTC_3 && getVersion().getMajor() == 3) || (requirement == ToolChainRequirement.SWIFTC_4 && getVersion().getMajor() == 4); + switch (requirement) { + case SWIFTC: + return true; + case SWIFTC_3: + return getVersion().getMajor() == 3; + case SWIFTC_4: + return getVersion().getMajor() == 4; + case SWIFTC_5: + return getVersion().getMajor() == 5; + case SWIFTC_4_OR_OLDER: + return getVersion().getMajor() < 5; + default: + return false; + } + } + } + + public static class InstalledXcode extends InstalledSwiftc { + private static final ProcessEnvironment PROCESS_ENVIRONMENT = NativeServicesTestFixture.getInstance().get(ProcessEnvironment.class); + private final File xcodeDir; + private String originalDeveloperDir; + + public InstalledXcode(File xcodeDir, VersionNumber compilerVersion) { + super(new File("/usr/bin"), compilerVersion); + this.xcodeDir = xcodeDir; + } + + @Override + public List getRuntimeEnv() { + List result = new ArrayList<>(); + result.addAll(super.getRuntimeEnv()); + result.add("DEVELOPER_DIR=" + xcodeDir.getAbsolutePath()); + return result; + } + + @Override + public void initialiseEnvironment() { + super.initialiseEnvironment(); + + originalDeveloperDir = System.getenv("DEVELOPER_DIR"); + System.out.println(String.format("Using DEVELOPER_DIR %s", xcodeDir.getAbsolutePath())); + PROCESS_ENVIRONMENT.setEnvironmentVariable("DEVELOPER_DIR", xcodeDir.getAbsolutePath()); + } + + @Override + public void resetEnvironment() { + if (originalDeveloperDir != null) { + PROCESS_ENVIRONMENT.setEnvironmentVariable("DEVELOPER_DIR", xcodeDir.getAbsolutePath()); + } + + super.resetEnvironment(); + } + + @Override + public void configureExecuter(GradleExecuter executer) { + super.configureExecuter(executer); + executer.withEnvironmentVars(ImmutableMap.of("DEVELOPER_DIR", xcodeDir.getAbsolutePath())); } } diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/NativeBinaryFixture.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/NativeBinaryFixture.groovy index 2016474094f66..e6c7eb6d1301a 100644 --- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/NativeBinaryFixture.groovy +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/NativeBinaryFixture.groovy @@ -154,14 +154,14 @@ class NativeBinaryFixture { BinaryInfo getBinaryInfo() { file.assertExists() if (OperatingSystem.current().isMacOsX()) { - return new OtoolBinaryInfo(file); + return new OtoolBinaryInfo(file, toolChain.runtimeEnv) } if (OperatingSystem.current().isWindows()) { if (toolChain.meets(ToolChainRequirement.GCC)) { - return new DumpbinGccProducedBinaryInfo(file); + return new DumpbinGccProducedBinaryInfo(file) } - return new DumpbinBinaryInfo(file); + return new DumpbinBinaryInfo(file) } - return new ReadelfBinaryInfo(file); + return new ReadelfBinaryInfo(file) } } diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/ToolChainRequirement.java b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/ToolChainRequirement.java index ee54adf8ca60e..45e746b5df411 100644 --- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/ToolChainRequirement.java +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/ToolChainRequirement.java @@ -49,6 +49,10 @@ public enum ToolChainRequirement { SWIFTC_3, // Any Swift 4.x compiler SWIFTC_4, + // Any available Swift compiler <= 4 + SWIFTC_4_OR_OLDER, + // Any Swift 5.x compiler + SWIFTC_5, // Supports building 32-bit binaries SUPPORTS_32, // Supports building both 32-bit and 64-bit binaries diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5.groovy new file mode 100644 index 0000000000000..cbbe417f4aff7 --- /dev/null +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5.groovy @@ -0,0 +1,60 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.nativeplatform.fixtures.app + +import org.gradle.integtests.fixtures.SourceFile + +class Swift5 extends SwiftSourceElement { + Swift5(String projectName) { + super(projectName) + } + + @Override + List getFiles() { + return [sourceFile("swift", "swift5-code.swift", ''' + public typealias Name = (firstName: String, lastName: String) + + public func getNames() -> [Name] { + return [("Bart", "den Hollander")] + } + + public func getLastNameOfFirstEntry(names: [Name]) -> String { + var result: String = "" + names.forEach({ name in + result = name.lastName // "den Hollander" + }) + return result + } + + public func getLongMessage() -> String { + return """ + When you write a string that spans multiple + lines make sure you start its content on a + line all of its own, and end it with three + quotes also on a line of their own. + Multi-line strings also let you write "quote marks" + freely inside your strings, which is great! + """ + } + + public func getRawString() -> String { + let value = 42 + return #"Raw string are ones with "quotes", backslash (\\), but can do special string interpolation (\\#(value))"# + } + ''')] + } +} diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5Test.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5Test.groovy new file mode 100644 index 0000000000000..84fad22658f01 --- /dev/null +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5Test.groovy @@ -0,0 +1,40 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.nativeplatform.fixtures.app + +class Swift5Test extends XCTestSourceFileElement { + Swift5Test() { + super("Swift5Test") + } + + @Override + List getTestCases() { + return [ + testCase("testRawStrings", + '''XCTAssertEqual(getRawString(), "Raw string are ones with \\"quotes\\", backslash (\\\\), but can do special string interpolation (42)"))'''), + testCase("testCodeWasCompiledWithSwift5Compiler", + """#if swift(>=6.0) + XCTFail("Compilation unit compiled with Swift 6+ instead of Swift 5.x"); + #elseif swift(>=5.0) + // Do nothing + #else + XCTFail("Compilation unit compiled with Swift 4- instead of Swift 5.x"); + #endif + """) + ] + } +} diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5WithSwift4XCTest.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5WithSwift4XCTest.groovy new file mode 100644 index 0000000000000..d80709f52982d --- /dev/null +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5WithSwift4XCTest.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.nativeplatform.fixtures.app + +class Swift5WithSwift4XCTest extends MainWithXCTestSourceElement { + final Swift5 main + final XCTestSourceElement test + + Swift5WithSwift4XCTest(String projectName) { + super(projectName) + this.main = new Swift5(projectName) + this.test = new XCTestSourceElement(projectName) { + @Override + List getTestSuites() { + return [new Swift4Test().withImport(main.moduleName)] + } + } + } +} diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5WithXCTest.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5WithXCTest.groovy new file mode 100644 index 0000000000000..077f42601a9c5 --- /dev/null +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5WithXCTest.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.nativeplatform.fixtures.app + +class Swift5WithXCTest extends MainWithXCTestSourceElement { + final Swift5 main + final XCTestSourceElement test + + Swift5WithXCTest(String projectName) { + super(projectName) + this.main = new Swift5(projectName) + this.test = new XCTestSourceElement(projectName) { + @Override + List getTestSuites() { + return [new Swift5Test().withImport(main.moduleName)] + } + } + } +} diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5XCTest.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5XCTest.groovy new file mode 100644 index 0000000000000..2c3d4d98108ab --- /dev/null +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/Swift5XCTest.groovy @@ -0,0 +1,36 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.nativeplatform.fixtures.app + +class Swift5XCTest extends XCTestSourceElement { + Swift5XCTest(String projectName) { + super(projectName) + } + + @Override + List getTestSuites() { + return [new XCTestSourceFileElement("Swift5Test") { + @Override + List getTestCases() { + return [testCase("testRawString", + '''let value = 42 + let rawString = #"Raw string are ones with "quotes", backslash (\\), but can do special string interpolation (\\#(value))"# + XCTAssertEqual(rawString, "Raw string are ones with \\"quotes\\", backslash (\\\\), but can do special string interpolation (42)")''')] + } + }] + } +} diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/SwiftCompilerDetectingApp.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/SwiftCompilerDetectingApp.groovy index d0bffe9e2215b..9b5ae16dafbff 100644 --- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/SwiftCompilerDetectingApp.groovy +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/app/SwiftCompilerDetectingApp.groovy @@ -28,7 +28,9 @@ class SwiftCompilerDetectingApp extends SourceFileElement implements AppElement @Override SourceFile getSourceFile() { return sourceFile('swift', 'main.swift', """ - #if swift(>=4.0) + #if swift(>=5.0) + print("Compiled using Swift 5.x compiler") + #elseif swift(>=4.0) print("Compiled using Swift 4.x compiler") #elseif swift(>=3.0) print("Compiled using Swift 3.x compiler") diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/binaryinfo/BinaryInfo.java b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/binaryinfo/BinaryInfo.java index d586890adbfac..5dba881a91490 100644 --- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/binaryinfo/BinaryInfo.java +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/binaryinfo/BinaryInfo.java @@ -16,6 +16,7 @@ package org.gradle.nativeplatform.fixtures.binaryinfo; +import com.google.api.client.util.Objects; import org.gradle.nativeplatform.platform.internal.ArchitectureInternal; import java.util.List; @@ -50,5 +51,9 @@ public char getType() { public boolean isExported() { return exported; } + + public String toString() { + return Objects.toStringHelper(this).add("name", name).add("type", type).add("exported", exported).toString(); + } } } diff --git a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/binaryinfo/OtoolBinaryInfo.groovy b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/binaryinfo/OtoolBinaryInfo.groovy index 8da014fc59c9e..7803e588e9ee3 100644 --- a/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/binaryinfo/OtoolBinaryInfo.groovy +++ b/subprojects/platform-native/src/testFixtures/groovy/org/gradle/nativeplatform/fixtures/binaryinfo/OtoolBinaryInfo.groovy @@ -21,13 +21,15 @@ import org.gradle.nativeplatform.platform.internal.Architectures class OtoolBinaryInfo implements BinaryInfo { def binaryFile + private final List environments - OtoolBinaryInfo(File binaryFile) { + OtoolBinaryInfo(File binaryFile, List environments) { this.binaryFile = binaryFile + this.environments = environments } ArchitectureInternal getArch() { - def process = ['otool', '-hv', binaryFile.absolutePath].execute() + def process = ['otool', '-hv', binaryFile.absolutePath].execute(environments, null) def lines = process.inputStream.readLines() def archString = lines.last().split()[1] @@ -42,18 +44,18 @@ class OtoolBinaryInfo implements BinaryInfo { } List listObjectFiles() { - def process = ['ar', '-t', binaryFile.getAbsolutePath()].execute() + def process = ['ar', '-t', binaryFile.getAbsolutePath()].execute(environments, null) return process.inputStream.readLines().drop(1) } List listLinkedLibraries() { - def process = ['otool', '-L', binaryFile.absolutePath].execute() + def process = ['otool', '-L', binaryFile.absolutePath].execute(environments, null) def lines = process.inputStream.readLines() return lines } List listSymbols() { - def process = ['nm', '-a', '-f', 'posix', binaryFile.absolutePath].execute() + def process = ['nm', '-a', '-f', 'posix', binaryFile.absolutePath].execute(environments, null) def lines = process.inputStream.readLines() return lines.collect { line -> // Looks like: @@ -71,14 +73,17 @@ class OtoolBinaryInfo implements BinaryInfo { } List listDwarfSymbols() { - def process = ['dwarfdump', '--diff', binaryFile.absolutePath].execute() + def process = ['dwarfdump', '--diff', binaryFile.absolutePath].execute(environments, null) def lines = process.inputStream.readLines() def symbols = [] lines.each { line -> - def findSymbol = (line =~ /.*AT_name\(\s+"(.*)"\s+\)/) + // The output changed on Apple toolchain (Xcode): + // 10.1 and earlier: ` AT_name( "<...>" )` + // 10.2 (and maybe later): ` DW_AT_NAME ("<...>")` + def findSymbol = (line =~ /.*(DW_)?AT_name\s*\(\s*"(.*)"\s*\)/) if (findSymbol.matches()) { - def name = new File(findSymbol[0][1] as String).name.trim() + def name = new File(findSymbol[0][2] as String).name.trim() symbols << new BinaryInfo.Symbol(name, 'D' as char, true) } } @@ -86,7 +91,7 @@ class OtoolBinaryInfo implements BinaryInfo { } String getSoName() { - def process = ['otool', '-D', binaryFile.absolutePath].execute() + def process = ['otool', '-D', binaryFile.absolutePath].execute(environments, null) def lines = process.inputStream.readLines() return lines[1] } diff --git a/subprojects/plugin-development/src/integTest/groovy/org/gradle/plugin/devel/tasks/ValidateTaskPropertiesIntegrationTest.groovy b/subprojects/plugin-development/src/integTest/groovy/org/gradle/plugin/devel/tasks/ValidateTaskPropertiesIntegrationTest.groovy index d9f80584aac04..2334d011e5b69 100644 --- a/subprojects/plugin-development/src/integTest/groovy/org/gradle/plugin/devel/tasks/ValidateTaskPropertiesIntegrationTest.groovy +++ b/subprojects/plugin-development/src/integTest/groovy/org/gradle/plugin/devel/tasks/ValidateTaskPropertiesIntegrationTest.groovy @@ -18,9 +18,9 @@ package org.gradle.plugin.devel.tasks import org.gradle.api.artifacts.transform.InputArtifact import org.gradle.api.artifacts.transform.InputArtifactDependencies -import org.gradle.api.artifacts.transform.TransformParameters import org.gradle.api.file.FileCollection import org.gradle.api.model.ObjectFactory +import org.gradle.api.model.ReplacedBy import org.gradle.api.tasks.Console import org.gradle.api.tasks.Destroys import org.gradle.api.tasks.Input @@ -152,6 +152,7 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { def "task can have property with annotation @#annotation.simpleName"() { file("src/main/java/MyTask.java") << """ import org.gradle.api.*; + import org.gradle.api.model.*; import org.gradle.api.tasks.*; public class MyTask extends DefaultTask { @@ -170,6 +171,7 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { Inject | Inject.name | ObjectFactory OptionValues | "${OptionValues.name}(\"a\")" | List Internal | 'Internal' | String + ReplacedBy | 'ReplacedBy("")' | String Console | 'Console' | Boolean Destroys | 'Destroys' | FileCollection LocalState | 'LocalState' | FileCollection @@ -227,7 +229,44 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { annotation | _ InputArtifact | _ InputArtifactDependencies | _ - TransformParameters | _ + } + + def "validates task caching annotations"() { + file("src/main/java/MyTask.java") << """ + import org.gradle.api.*; + import org.gradle.api.tasks.*; + import org.gradle.api.artifacts.transform.*; + + @CacheableTransform + public class MyTask extends DefaultTask { + @Nested + Options getOptions() { + return null; + } + + @CacheableTask @CacheableTransform + public static class Options { + @Input + String getNestedThing() { + return null; + } + } + } + """ + + expect: + fails("validateTaskProperties") + failure.assertHasDescription("Execution failed for task ':validateTaskProperties'.") + failure.assertHasCause("Task property validation failed. See") + failure.assertHasCause("Error: Cannot use @CacheableTask with type MyTask.Options. This annotation can only be used with Task types.") + failure.assertHasCause("Error: Cannot use @CacheableTransform with type MyTask. This annotation can only be used with TransformAction types.") + failure.assertHasCause("Error: Cannot use @CacheableTransform with type MyTask.Options. This annotation can only be used with TransformAction types.") + + file("build/reports/task-properties/report.txt").text == """ + Error: Cannot use @CacheableTask with type MyTask.Options. This annotation can only be used with Task types. + Error: Cannot use @CacheableTransform with type MyTask. This annotation can only be used with TransformAction types. + Error: Cannot use @CacheableTransform with type MyTask.Options. This annotation can only be used with TransformAction types. + """.stripIndent().trim() } def "detects missing annotation on Groovy properties"() { @@ -352,6 +391,8 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { file("src/main/java/MyTask.java") << """ import org.gradle.api.*; import org.gradle.api.tasks.*; + import java.util.Set; + import java.util.Collections; import java.io.File; @CacheableTask @@ -393,6 +434,16 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { public File getInputDirectory() { return new File("inputDir"); } + + @InputFile + public File getInputFile() { + return new File("inputFile"); + } + + @InputFiles + public Set getInputFiles() { + return Collections.emptySet(); + } @Input public File getFile() { @@ -412,6 +463,8 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { Warning: Task type 'MyTask': property 'badTime' is not annotated with an input or output annotation. Warning: Task type 'MyTask': property 'file' has @Input annotation used on property of type java.io.File. Warning: Task type 'MyTask': property 'inputDirectory' is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE. + Warning: Task type 'MyTask': property 'inputFile' is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE. + Warning: Task type 'MyTask': property 'inputFiles' is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE. Warning: Task type 'MyTask': property 'options.badNested' is not annotated with an input or output annotation. """.stripIndent().trim() } @@ -549,7 +602,7 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { compile localGroovy() } - validateTaskProperties.enableStricterValidation = true + validateTaskProperties.enableStricterValidation = project.hasProperty('strict') """ file("src/main/groovy/MyTask.groovy") << """ import org.gradle.api.* @@ -557,19 +610,140 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { class MyTask extends DefaultTask { @InputFile - File missingNormalization + File fileProp + + @InputFiles + Set filesProp + + @InputDirectory + File dirProp @javax.inject.Inject org.gradle.api.internal.file.FileResolver fileResolver } """ + expect: + succeeds("validateTaskProperties") + when: - fails "validateTaskProperties" + fails "validateTaskProperties", "-Pstrict" then: file("build/reports/task-properties/report.txt").text == """ - Warning: Task type 'MyTask': property 'missingNormalization' is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE. + Warning: Task type 'MyTask': property 'dirProp' is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE. + Warning: Task type 'MyTask': property 'fileProp' is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE. + Warning: Task type 'MyTask': property 'filesProp' is missing a @PathSensitive annotation, defaulting to PathSensitivity.ABSOLUTE. + """.stripIndent().trim() + } + + def "can validate properties of an artifact transform action"() { + file("src/main/java/MyTransformAction.java") << """ + import org.gradle.api.*; + import org.gradle.api.tasks.*; + import org.gradle.api.artifacts.transform.*; + import java.io.*; + + public abstract class MyTransformAction implements TransformAction { + // Should be ignored because it's not a getter + public void getVoid() { + } + + // Should be ignored because it's not a getter + public int getWithParameter(int count) { + return count; + } + + // Ignored because static + public static int getStatic() { + return 0; + } + + // Ignored because injected + @javax.inject.Inject + public abstract org.gradle.api.internal.file.FileResolver getInjected(); + + // Valid because it is annotated + @InputArtifact + public abstract File getGoodInput(); + + // Invalid because it has no annotation + public long getBadTime() { + return System.currentTimeMillis(); + } + + // Invalid because it has some other annotation + @Deprecated + public String getOldThing() { + return null; + } + + // Unsupported annotation + @InputFile + public abstract File getInputFile(); + } + """ + + expect: + fails "validateTaskProperties" + failure.assertHasCause "Task property validation failed" + failure.assertHasCause "Error: Type 'MyTransformAction': property 'badTime' is not annotated with an input annotation." + failure.assertHasCause "Error: Type 'MyTransformAction': property 'inputFile' is annotated with unsupported annotation @InputFile." + failure.assertHasCause "Error: Type 'MyTransformAction': property 'oldThing' is not annotated with an input annotation." + + file("build/reports/task-properties/report.txt").text == """ + Error: Type 'MyTransformAction': property 'badTime' is not annotated with an input annotation. + Error: Type 'MyTransformAction': property 'inputFile' is annotated with unsupported annotation @InputFile. + Error: Type 'MyTransformAction': property 'oldThing' is not annotated with an input annotation. + """.stripIndent().trim() + } + + def "can validate properties of an artifact transform parameters object"() { + file("src/main/java/MyTransformParameters.java") << """ + import org.gradle.api.*; + import org.gradle.api.tasks.*; + import org.gradle.api.artifacts.transform.*; + import java.io.*; + + public interface MyTransformParameters extends TransformParameters { + // Should be ignored because it's not a getter + void getVoid(); + + // Should be ignored because it's not a getter + int getWithParameter(int count); + + // Ignored because injected + @javax.inject.Inject + org.gradle.api.internal.file.FileResolver getInjected(); + + // Valid because it is annotated + @InputFile + File getGoodInput(); + + // Invalid because it has no annotation + long getBadTime(); + + // Invalid because it has some other annotation + @Deprecated + String getOldThing(); + + // Unsupported annotation + @InputArtifact + File getInputFile(); + } + """ + + expect: + fails "validateTaskProperties" + failure.assertHasCause "Task property validation failed" + failure.assertHasCause "Error: Type 'MyTransformParameters': property 'badTime' is not annotated with an input annotation." + failure.assertHasCause "Error: Type 'MyTransformParameters': property 'inputFile' is annotated with unsupported annotation @InputArtifact." + failure.assertHasCause "Error: Type 'MyTransformParameters': property 'oldThing' is not annotated with an input annotation." + + file("build/reports/task-properties/report.txt").text == """ + Error: Type 'MyTransformParameters': property 'badTime' is not annotated with an input annotation. + Error: Type 'MyTransformParameters': property 'inputFile' is annotated with unsupported annotation @InputArtifact. + Error: Type 'MyTransformParameters': property 'oldThing' is not annotated with an input annotation. """.stripIndent().trim() } @@ -590,4 +764,40 @@ class ValidateTaskPropertiesIntegrationTest extends AbstractIntegrationSpec { 'classes' | 'output.classesDirs' 'classpath' | ' compileClasspath ' } + + def "reports conflicting types when property is replaced but keeps old annotations"() { + file("src/main/java/MyTask.java") << """ + import org.gradle.api.*; + import org.gradle.api.model.*; + import org.gradle.api.tasks.*; + import org.gradle.api.provider.*; + + public class MyTask extends DefaultTask { + private final Property newProperty = getProject().getObjects().property(String.class); + + @Input + @ReplacedBy("newProperty") + public String getOldProperty() { + return newProperty.get(); + } + + public void setOldProperty(String oldProperty) { + newProperty.set(oldProperty); + } + + @Input + public Property getNewProperty() { + return newProperty; + } + } + """ + + when: + fails "validateTaskProperties" + + then: + file("build/reports/task-properties/report.txt").text == """ + Warning: Task type 'MyTask': property 'oldProperty' has conflicting property types declared: @Input, @ReplacedBy. + """.stripIndent().trim() + } } diff --git a/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/tasks/ValidateTaskProperties.java b/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/tasks/ValidateTaskProperties.java index 54e0ead044c71..59bed88ead63d 100644 --- a/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/tasks/ValidateTaskProperties.java +++ b/subprojects/plugin-development/src/main/java/org/gradle/plugin/devel/tasks/ValidateTaskProperties.java @@ -27,7 +27,6 @@ import org.gradle.api.GradleException; import org.gradle.api.Incubating; import org.gradle.api.InvalidUserDataException; -import org.gradle.api.Task; import org.gradle.api.UncheckedIOException; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.EmptyFileVisitor; @@ -49,7 +48,6 @@ import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.TaskValidationException; import org.gradle.api.tasks.VerificationTask; -import org.gradle.internal.Cast; import org.gradle.internal.classanalysis.AsmConstants; import org.gradle.internal.classloader.ClassLoaderFactory; import org.gradle.internal.classloader.ClassLoaderUtils; @@ -65,7 +63,6 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.lang.reflect.Modifier; import java.util.Collection; import java.util.List; import java.util.Map; @@ -102,6 +99,7 @@ *
  • {@literal @}{@link javax.inject.Inject} marks a Gradle service used by the task.
  • *
  • {@literal @}{@link org.gradle.api.tasks.Console Console} marks a property that only influences the console output of the task.
  • *
  • {@literal @}{@link org.gradle.api.tasks.Internal Internal} mark an internal property of the task.
  • + *
  • {@literal @}{@link org.gradle.api.model.ReplacedBy ReplacedBy} mark a property as replaced by another (similar to {@code Internal}).
  • * * * @@ -141,12 +139,10 @@ public void validateTaskClasses() throws IOException { private void validateTaskClasses(final ClassLoader classLoader) throws IOException { final Map taskValidationProblems = Maps.newTreeMap(); - final Class taskInterface; final Method validatorMethod; try { - taskInterface = classLoader.loadClass(Task.class.getName()); Class validatorClass = classLoader.loadClass("org.gradle.api.internal.tasks.properties.PropertyValidationAccess"); - validatorMethod = validatorClass.getMethod("collectTaskValidationProblems", Class.class, Map.class, Boolean.TYPE); + validatorMethod = validatorClass.getMethod("collectValidationProblems", Class.class, Map.class, Boolean.TYPE); } catch (ClassNotFoundException | NoSuchMethodException e) { throw new RuntimeException(e); } @@ -176,18 +172,8 @@ public void visitFile(FileVisitDetails fileDetails) { } catch (NoClassDefFoundError e) { throw new GradleException("Could not load class: " + className, e); } - if (!Modifier.isPublic(clazz.getModifiers())) { - continue; - } - if (Modifier.isAbstract(clazz.getModifiers())) { - continue; - } - if (!taskInterface.isAssignableFrom(clazz)) { - continue; - } - Class taskClass = Cast.uncheckedCast(clazz); try { - validatorMethod.invoke(null, taskClass, taskValidationProblems, enableStricterValidation); + validatorMethod.invoke(null, clazz, taskValidationProblems, enableStricterValidation); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { diff --git a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/autoapply/AutoAppliedPluginsFunctionalTest.groovy b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/autoapply/AutoAppliedPluginsFunctionalTest.groovy index b70e432388420..36ef5ff9624bb 100644 --- a/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/autoapply/AutoAppliedPluginsFunctionalTest.groovy +++ b/subprojects/plugin-use/src/integTest/groovy/org/gradle/plugin/autoapply/AutoAppliedPluginsFunctionalTest.groovy @@ -84,11 +84,9 @@ class AutoAppliedPluginsFunctionalTest extends AbstractPluginIntegrationTest { def result = gradleHandle.waitForFinish() result.assertHasPostBuildOutput(BUILD_SCAN_LICENSE_QUESTION) result.assertHasPostBuildOutput(BUILD_SCAN_LICENSE_DECLINATION) - result.assertHasPostBuildOutput(BUILD_SCAN_PLUGIN_CONFIG_PROBLEM) result.assertNotOutput(BUILD_SCAN_SUCCESSFUL_PUBLISHING) result.assertNotOutput(BUILD_SCAN_LICENSE_NOTE) - result.assertHasPostBuildOutput("The buildScan extension 'termsOfServiceAgree' value must be exactly the string 'yes' (without quotes).") - result.assertHasPostBuildOutput("The value given was 'no'.") + result.assertHasPostBuildOutput("You must answer 'yes' to publish a build scan when prompted on the command line or accept the Gradle Terms of Service in a buildScan configuration block.") } def "can auto-apply build scan plugin and cancel license acceptance with ctrl-d in interactive console"() { diff --git a/subprojects/plugins/plugins.gradle.kts b/subprojects/plugins/plugins.gradle.kts index 0fbeb58038e6d..b86d28d95ce12 100644 --- a/subprojects/plugins/plugins.gradle.kts +++ b/subprojects/plugins/plugins.gradle.kts @@ -1,5 +1,5 @@ -import org.gradle.gradlebuild.unittestandcompile.ModuleType import org.gradle.gradlebuild.testing.integrationtests.cleanup.WhenNotEmpty +import org.gradle.gradlebuild.unittestandcompile.ModuleType /* * Copyright 2010 the original author or authors. @@ -38,8 +38,6 @@ dependencies { compile(library("commons_lang")) compile(library("slf4j_api")) - runtime(library("commons_cli")) - // This dependency makes the services provided by `:compositeBuilds` available at runtime for all integration tests in all subprojects // Making this better would likely involve a separate `:gradleRuntime` module that brings in `:core`, `:dependencyManagement` and other key subprojects runtime(project(":compositeBuilds")) diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy index 326970bae51d3..4cd54a0b7be75 100644 --- a/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/api/plugins/BasePluginIntegrationTest.groovy @@ -79,4 +79,24 @@ class BasePluginIntegrationTest extends AbstractIntegrationSpec { expect: succeeds "tasks" } + + def "can override archiveBaseName in custom Jar task"() { + buildFile << """ + apply plugin: 'base' + class MyJar extends Jar { + MyJar() { + super() + archiveBaseName.set("myjar") + } + } + task myJar(type: MyJar) + task assertCheck { + doLast { + assert tasks.myJar.archiveBaseName.get() == "myjar" + } + } + """ + expect: + succeeds("assertCheck") + } } diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyJavaLibraryInteractionIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyJavaLibraryInteractionIntegrationTest.groovy new file mode 100644 index 0000000000000..0e601c5bdeae6 --- /dev/null +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/groovy/GroovyJavaLibraryInteractionIntegrationTest.groovy @@ -0,0 +1,112 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.groovy + +import org.gradle.api.JavaVersion +import org.gradle.integtests.fixtures.AbstractDependencyResolutionTest +import org.gradle.integtests.fixtures.resolve.ResolveTestFixture +import org.gradle.test.fixtures.archive.JarTestFixture +import spock.lang.Issue +import spock.lang.Unroll + +class GroovyJavaLibraryInteractionIntegrationTest extends AbstractDependencyResolutionTest { + + ResolveTestFixture resolve = new ResolveTestFixture(buildFile, "compileClasspath") + + def setup() { + settingsFile << """ + rootProject.name = 'test' + """ + resolve.prepare() + } + + @Issue("https://github.com/gradle/gradle/issues/7398") + @Unroll + def "selects #expected output when #consumerPlugin plugin adds a project dependency to #consumerConf and producer has java-library=#groovyWithJavaLib"( + String consumerPlugin, String consumerConf, boolean groovyWithJavaLib, String expected) { + given: + multiProjectBuild('issue7398', ['groovyLib', 'javaLib']) { + file('groovyLib').with { + file('src/main/groovy/GroovyClass.groovy') << "public class GroovyClass {}" + file('build.gradle') << """ + ${groovyWithJavaLib ? "apply plugin: 'java-library'" : ''} + apply plugin: 'groovy' + dependencies { + implementation localGroovy() + } + """ + } + file('javaLib').with { + file('src/main/java/JavaClass.java') << "public class JavaClass { GroovyClass reference; }" + file('build.gradle') << """ + apply plugin: '$consumerPlugin' + dependencies { + $consumerConf project(':groovyLib') + } + """ + } + } + when: + succeeds 'javaLib:checkDeps' + + if (expected == 'jar') { + def jar = new JarTestFixture(testDirectory.file("groovyLib/build/libs/groovyLib-1.0.jar")) + executedAndNotSkipped(":groovyLib:compileJava", ":groovyLib:compileGroovy", ":groovyLib:classes", ":groovyLib:jar") + jar.hasDescendants("Dummy.class", "GroovyClass.class") + } else { + executedAndNotSkipped(":groovyLib:compileJava", ":groovyLib:compileGroovy") + notExecuted(":groovyLib:classes", ":groovyLib:jar") + } + + then: + resolve.expectGraph { + root(":javaLib", "org.test:javaLib:1.0") { + project(":groovyLib", "org.test:groovyLib:1.0") { + variant("apiElements", [ + 'org.gradle.category': 'library', + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': JavaVersion.current().majorVersion, + 'org.gradle.usage': "java-api-jars"]) // this is a bit curious, it's an artifact of how selection is done + switch (expected) { + case "jar": + artifact(name: "groovyLib") + break + case "classes": + // first one is "main" from Java sources + artifact(name: 'main', noType: true) + // second one is "main" from Groovy sources + artifact(name: 'main', noType: true) + break + } + } + } + } + + where: + consumerPlugin | consumerConf | groovyWithJavaLib | expected + 'java-library' | 'api' | true | "classes" + 'java-library' | 'api' | false | "jar" + 'java-library' | 'compile' | true | "classes" + 'java-library' | 'compile' | false | "jar" + 'java-library' | 'implementation' | true | "classes" + 'java-library' | 'implementation' | false | "jar" + + 'java' | 'compile' | true | "classes" + 'java' | 'compile' | false | "jar" + 'java' | 'implementation' | true | "classes" + 'java' | 'implementation' | false | "jar" + } +} diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaApplicationOutgoingVariantsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaApplicationOutgoingVariantsIntegrationTest.groovy index b34f3d3cb5a68..3a7a7467ea407 100644 --- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaApplicationOutgoingVariantsIntegrationTest.groovy +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaApplicationOutgoingVariantsIntegrationTest.groovy @@ -16,6 +16,7 @@ package org.gradle.java +import org.gradle.api.JavaVersion import org.gradle.integtests.fixtures.AbstractIntegrationSpec import spock.lang.Unroll @@ -78,8 +79,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") when: buildFile << """ @@ -96,8 +97,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") } @Unroll @@ -117,8 +118,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, main, runtime-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-classes}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-jars}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-classes}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-jars}") when: buildFile << """ @@ -135,8 +136,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, main, runtime-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-classes}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-jars}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-classes}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-jars}") where: usage | _ @@ -160,8 +161,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") when: buildFile << """ @@ -178,8 +179,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") where: usage | _ @@ -201,8 +202,8 @@ project(':consumer') { result.assertTasksExecuted(":other-java:compileJava", ":other-java:processResources", ":other-java:classes", ":other-java:jar", ":java:compileJava", ":java:processResources", ":java:classes", ":java:jar", ":consumer:resolve") outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") } def "provides runtime classes variant"() { @@ -221,8 +222,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") - outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") when: buildFile << """ @@ -239,8 +240,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") - outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") } def "provides runtime resources variant"() { @@ -259,8 +260,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") - outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") when: buildFile << """ @@ -277,7 +278,11 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") - outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + } + + static String defaultTargetPlatform() { + "org.gradle.jvm.version=${JavaVersion.current().majorVersion}" } } diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaIncompatiblePluginsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaIncompatiblePluginsIntegrationTest.groovy new file mode 100644 index 0000000000000..4f8ee3519c2c5 --- /dev/null +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaIncompatiblePluginsIntegrationTest.groovy @@ -0,0 +1,57 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.java + +import org.gradle.integtests.fixtures.AbstractIntegrationSpec +import spock.lang.Unroll + +class JavaIncompatiblePluginsIntegrationTest extends AbstractIntegrationSpec { + + @Unroll + def "nag users when applying both java-platform and #plugin"() { + when: + buildFile << """ +plugins { + id 'java-platform' + id '${plugin}' +} +""" + then: + executer.expectDeprecationWarning() + succeeds 'help' + + where: + plugin << ['java', 'java-library'] + } + + @Unroll + def "cannot apply both #plugin and java-platform"() { + when: + buildFile << """ +plugins { + id '${plugin}' + id 'java-platform' +} +""" + then: + fails 'help' + failureHasCause("The \"java-platform\" plugin cannot be applied together with the \"java\" (or \"java-library\") plugin") + + where: + plugin << ['java', 'java-library'] + } +} diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryCrossProjectTargetJvmVersionIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryCrossProjectTargetJvmVersionIntegrationTest.groovy new file mode 100644 index 0000000000000..ab5cb231fd9bb --- /dev/null +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryCrossProjectTargetJvmVersionIntegrationTest.groovy @@ -0,0 +1,179 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.java + +import org.gradle.integtests.fixtures.AbstractIntegrationSpec +import org.gradle.integtests.fixtures.resolve.ResolveTestFixture +import spock.lang.Unroll + +class JavaLibraryCrossProjectTargetJvmVersionIntegrationTest extends AbstractIntegrationSpec { + ResolveTestFixture resolve + + def setup() { + settingsFile << """ + rootProject.name = 'test' + include 'producer' + """ + buildFile << """ + allprojects { + apply plugin: 'java-library' + } + + dependencies { + api project(':producer') + } + """ + resolve = new ResolveTestFixture(buildFile, 'compileClasspath') + resolve.prepare() + } + + def "can fail resolution if producer doesn't have appropriate target version"() { + file('producer/build.gradle') << """ + java { + sourceCompatibility = JavaVersion.VERSION_1_7 + targetCompatibility = JavaVersion.VERSION_1_7 + } + """ + buildFile << """ + configurations.compileClasspath.attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 6) + """ + + when: + fails ':checkDeps' + + then: + failure.assertHasCause('''Unable to find a matching variant of project :producer: + - Variant 'apiElements' capability test:producer:unspecified: + - Found org.gradle.category 'library' but wasn't required. + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '6' and found incompatible value '7'. + - Required org.gradle.usage 'java-api' and found compatible value 'java-api-jars'. + - Variant 'runtimeElements' capability test:producer:unspecified: + - Found org.gradle.category 'library' but wasn't required. + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '6' and found incompatible value '7'. + - Required org.gradle.usage 'java-api' and found compatible value 'java-runtime-jars'.''') + } + + @Unroll + def "can select the most appropriate producer variant (#expected) based on target compatibility (#requested)"() { + file('producer/build.gradle') << """ + // avoid test noise so that typically version 8 is not selected when running on JDK 8 + configurations.apiElements.attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 1000) + configurations.runtimeElements.attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 1000) + + [6, 7, 9].each { v -> + configurations { + "apiElementsJdk\${v}" { + canBeConsumed = true + canBeResolved = false + attributes { + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'java-api-jars')) + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, 'external')) + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, v) + } + } + } + artifacts { + "apiElementsJdk\${v}" file("producer-jdk\${v}.jar") + } + } + """ + buildFile << """ + configurations.compileClasspath.attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, $requested) + """ + + when: + run ':checkDeps' + + then: + resolve.expectGraph { + root(":", ":test:") { + project(':producer', 'test:producer:') { + variant(expected, [ + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': selected, + 'org.gradle.usage':'java-api-jars' + ]) + artifact(classifier: "jdk${selected}") + } + } + } + + where: + requested | selected + 6 | 6 + 7 | 7 + 8 | 7 + 9 | 9 + 10 | 9 + + expected = "apiElementsJdk$selected" + } + + def "can disable automatic setting of target JVM attribute"() { + file("producer/build.gradle") << """ + java { + targetCompatibility = JavaVersion.VERSION_1_7 + } + """ + buildFile << """ + java { + targetCompatibility = JavaVersion.VERSION_1_6 + } + """ + + when: + fails ':checkDeps' + + then: + failure.assertHasCause("""Unable to find a matching variant of project :producer: + - Variant 'apiElements' capability test:producer:unspecified: + - Found org.gradle.category 'library' but wasn't required. + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '6' and found incompatible value '7'. + - Required org.gradle.usage 'java-api' and found compatible value 'java-api-jars'. + - Variant 'runtimeElements' capability test:producer:unspecified: + - Found org.gradle.category 'library' but wasn't required. + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '6' and found incompatible value '7'. + - Required org.gradle.usage 'java-api' and found compatible value 'java-runtime-jars'.""") + + when: + buildFile << """ + java { + disableAutoTargetJvm() + } + """ + run ':checkDeps' + + then: + resolve.expectGraph { + root(":", ":test:") { + project(':producer', 'test:producer:') { + variant("apiElements", [ + 'org.gradle.category': 'library', + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': 7, + 'org.gradle.usage':'java-api-jars' + ]) + artifact group:'', module:'', version: '', type: '', name: 'main', noType: true + } + } + } + } +} diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryOutgoingVariantsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryOutgoingVariantsIntegrationTest.groovy index f890ac6ffb610..d6e6504f7b838 100644 --- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryOutgoingVariantsIntegrationTest.groovy +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryOutgoingVariantsIntegrationTest.groovy @@ -16,6 +16,7 @@ package org.gradle.java +import org.gradle.api.JavaVersion import org.gradle.integtests.fixtures.AbstractIntegrationSpec import spock.lang.Unroll @@ -80,8 +81,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, api-1.0.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") when: buildFile << """ @@ -98,8 +99,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, api-1.0.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") } @Unroll @@ -118,8 +119,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, api-1.0.jar, compile-1.0.jar, main, runtime-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-classes}") - outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-classes}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-classes}") + outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-classes}") when: buildFile << """ @@ -136,8 +137,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, api-1.0.jar, compile-1.0.jar, main, runtime-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-classes}") - outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-classes}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-classes}") + outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-classes}") where: usage | _ @@ -161,8 +162,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, api-1.0.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") when: buildFile << """ @@ -179,8 +180,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, api-1.0.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") where: usage | _ @@ -202,8 +203,8 @@ project(':consumer') { result.assertTasksExecuted(":other-java:compileJava", ":other-java:processResources", ":other-java:classes", ":other-java:jar", ":java:compileJava", ":java:processResources", ":java:classes", ":java:jar", ":consumer:resolve") outputContains("files: [java.jar, file-dep.jar, api-1.0.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") } def "provides runtime classes variant"() { @@ -222,8 +223,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, api-1.0.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") - outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") when: buildFile << """ @@ -240,8 +241,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, api-1.0.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") - outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") } def "provides runtime resources variant"() { @@ -260,8 +261,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, api-1.0.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") - outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") when: buildFile << """ @@ -278,7 +279,11 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, api-1.0.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") - outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + } + + static String defaultTargetPlatform() { + "org.gradle.jvm.version=${JavaVersion.current().majorVersion}" } } diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryPublishedTargetJvmVersionIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryPublishedTargetJvmVersionIntegrationTest.groovy new file mode 100644 index 0000000000000..4d314debbf94f --- /dev/null +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaLibraryPublishedTargetJvmVersionIntegrationTest.groovy @@ -0,0 +1,162 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.java + +import org.gradle.integtests.fixtures.AbstractHttpDependencyResolutionTest +import org.gradle.integtests.fixtures.resolve.ResolveTestFixture +import org.gradle.test.fixtures.server.http.MavenHttpModule +import spock.lang.Unroll + +class JavaLibraryPublishedTargetJvmVersionIntegrationTest extends AbstractHttpDependencyResolutionTest { + ResolveTestFixture resolve + MavenHttpModule module + + def setup() { + settingsFile << """ + rootProject.name = 'test' + """ + buildFile << """ + apply plugin: 'java-library' + + repositories { + maven { url '${mavenHttpRepo.uri}' } + } + + dependencies { + api 'org:producer:1.0' + } + """ + + resolve = new ResolveTestFixture(buildFile, 'compileClasspath') + resolve.prepare() + + module = mavenHttpRepo.module('org', 'producer', '1.0') + .withModuleMetadata() + .withGradleMetadataRedirection() + .adhocVariants() + .variant("apiElementsJdk6", [ + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': 6, + 'org.gradle.usage': 'java-api-jars'], { artifact('producer-1.0-jdk6.jar') }) + .variant("apiElementsJdk7", [ + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': 7, + 'org.gradle.usage': 'java-api-jars'], { artifact('producer-1.0-jdk7.jar') }) + .variant("apiElementsJdk9", [ + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': 9, + 'org.gradle.usage': 'java-api-jars'], { artifact('producer-1.0-jdk9.jar') }) + .variant("runtimeElementsJdk6", [ + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': 6, + 'org.gradle.usage': 'java-runtime-jars'], { artifact('producer-1.0-jdk6.jar') }) + .variant("runtimeElementsJdk7", [ + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': 7, + 'org.gradle.usage': 'java-runtime-jars'], { artifact('producer-1.0-jdk7.jar') }) + .variant("runtimeElementsJdk9", [ + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': 9, + 'org.gradle.usage': 'java-runtime-jars'], { artifact('producer-1.0-jdk9.jar') }) + .publish() + + } + + def "can fail resolution if producer doesn't have appropriate target version"() { + buildFile << """ + configurations.compileClasspath.attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 5) + """ + + when: + module.pom.expectGet() + module.moduleMetadata.expectGet() + + fails ':checkDeps' + + then: + failure.assertHasCause('''Unable to find a matching variant of org:producer:1.0: + - Variant 'apiElementsJdk6' capability org:producer:1.0: + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '5' and found incompatible value '6'. + - Found org.gradle.status 'release' but wasn't required. + - Required org.gradle.usage 'java-api' and found compatible value 'java-api-jars'. + - Variant 'apiElementsJdk7' capability org:producer:1.0: + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '5' and found incompatible value '7'. + - Found org.gradle.status 'release' but wasn't required. + - Required org.gradle.usage 'java-api' and found compatible value 'java-api-jars'. + - Variant 'apiElementsJdk9' capability org:producer:1.0: + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '5' and found incompatible value '9'. + - Found org.gradle.status 'release' but wasn't required. + - Required org.gradle.usage 'java-api' and found compatible value 'java-api-jars'. + - Variant 'runtimeElementsJdk6' capability org:producer:1.0: + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '5' and found incompatible value '6'. + - Found org.gradle.status 'release' but wasn't required. + - Required org.gradle.usage 'java-api' and found compatible value 'java-runtime-jars'. + - Variant 'runtimeElementsJdk7' capability org:producer:1.0: + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '5' and found incompatible value '7'. + - Found org.gradle.status 'release' but wasn't required. + - Required org.gradle.usage 'java-api' and found compatible value 'java-runtime-jars'. + - Variant 'runtimeElementsJdk9' capability org:producer:1.0: + - Required org.gradle.dependency.bundling 'external' and found compatible value 'external'. + - Required org.gradle.jvm.version '5' and found incompatible value '9'. + - Found org.gradle.status 'release' but wasn't required. + - Required org.gradle.usage 'java-api' and found compatible value 'java-runtime-jars'.''') + } + + @Unroll + def "can select the most appropriate producer variant (#expected) based on target compatibility (#requested)"() { + buildFile << """ + configurations.compileClasspath.attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, $requested) + """ + + when: + module.pom.expectGet() + module.moduleMetadata.expectGet() + module.getArtifact(classifier: "jdk${selected}").expectGet() + + run ':checkDeps' + + then: + resolve.expectGraph { + root(":", ":test:") { + module('org:producer:1.0') { + variant(expected, [ + 'org.gradle.dependency.bundling': 'external', + 'org.gradle.jvm.version': selected, + 'org.gradle.usage': 'java-api-jars', + 'org.gradle.status': 'release' + ]) + artifact(classifier: "jdk${selected}") + } + } + } + + where: + requested | selected + 6 | 6 + 7 | 7 + 8 | 7 + 9 | 9 + 10 | 9 + + expected = "apiElementsJdk$selected" + } +} diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaProjectOutgoingVariantsIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaProjectOutgoingVariantsIntegrationTest.groovy index cb976980b6a74..a7edbd6d46485 100644 --- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaProjectOutgoingVariantsIntegrationTest.groovy +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/JavaProjectOutgoingVariantsIntegrationTest.groovy @@ -16,6 +16,7 @@ package org.gradle.java +import org.gradle.api.JavaVersion import org.gradle.integtests.fixtures.AbstractIntegrationSpec import spock.lang.Unroll @@ -78,8 +79,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") when: buildFile << """ @@ -96,8 +97,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") } @Unroll @@ -116,8 +117,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, runtime-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-jars}") when: buildFile << """ @@ -134,8 +135,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, runtime-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-api-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-api-jars}") where: usage | _ @@ -160,8 +161,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") when: buildFile << """ @@ -178,8 +179,8 @@ project(':consumer') { outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") where: usage | _ @@ -201,8 +202,8 @@ project(':consumer') { result.assertTasksExecuted(":other-java:compileJava", ":other-java:processResources", ":other-java:classes", ":other-java:jar", ":java:compileJava", ":java:processResources", ":java:classes", ":java:jar", ":consumer:resolve") outputContains("files: [java.jar, file-dep.jar, compile-1.0.jar, other-java.jar, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") - outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") - outputContains("java.jar (project :java) {artifactType=jar, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-jars}") + outputContains("other-java.jar (project :other-java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") + outputContains("java.jar (project :java) {artifactType=jar, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-jars}") } def "provides runtime classes variant"() { @@ -220,8 +221,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") - outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") when: buildFile << """ @@ -238,8 +239,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") - outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :other-java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") + outputContains("main (project :java) {artifactType=java-classes-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-classes}") } def "provides runtime resources variant"() { @@ -258,8 +259,8 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar}") - outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") - outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") when: buildFile << """ @@ -276,7 +277,11 @@ project(':consumer') { outputContains("files: [main, file-dep.jar, compile-1.0.jar, main, implementation-1.0.jar, runtime-1.0.jar, runtime-only-1.0.jar]") outputContains("file-dep.jar {artifactType=jar, org.gradle.usage=java-runtime-jars}") outputContains("compile.jar (test:compile:1.0) {artifactType=jar, org.gradle.usage=java-runtime-jars}") - outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") - outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.dependency.bundling=external, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :other-java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + outputContains("main (project :java) {artifactType=java-resources-directory, org.gradle.category=library, org.gradle.dependency.bundling=external, ${defaultTargetPlatform()}, org.gradle.usage=java-runtime-resources}") + } + + static String defaultTargetPlatform() { + "org.gradle.jvm.version=${JavaVersion.current().majorVersion}" } } diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/ParallelTestTaskIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/ParallelTestTaskIntegrationTest.groovy index f163ce4d12fd4..9e331d13b3464 100644 --- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/ParallelTestTaskIntegrationTest.groovy +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/ParallelTestTaskIntegrationTest.groovy @@ -30,7 +30,7 @@ import java.util.concurrent.CountDownLatch @IgnoreIf({ !GradleContextualExecuter.isParallel() }) class ParallelTestTaskIntegrationTest extends AbstractIntegrationSpec { String getVersion() { - return "1.6" + return "1.7" } JavaVersion getJavaVersion() { diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy index 88ec847de1779..2f4f491986ff3 100644 --- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/BasicJavaCompilerIntegrationSpec.groovy @@ -146,7 +146,7 @@ public class FxApp extends Application { given: goodCode() buildFile << """ -compileJava.options.compilerArgs.addAll(['--release', '7']) +compileJava.options.compilerArgs.addAll(['--release', '8']) """ expect: @@ -156,27 +156,28 @@ compileJava.options.compilerArgs.addAll(['--release', '7']) @Requires(TestPrecondition.JDK9_OR_LATER) def "compile fails when using newer API with release option"() { given: - file("src/main/java/compile/test/FailsOnJava7.java") << ''' + file("src/main/java/compile/test/FailsOnJava8.java") << ''' package compile.test; -import java.util.Optional; +import java.util.stream.Stream; +import java.util.function.Predicate; -public class FailsOnJava7 { - public Optional someOptional() { - return Optional.of("Hello"); +public class FailsOnJava8 { + public Stream takeFromStream(Stream stream) { + return stream.takeWhile(Predicate.isEqual("foo")); } } ''' buildFile << """ -compileJava.options.compilerArgs.addAll(['--release', '7']) +compileJava.options.compilerArgs.addAll(['--release', '8']) """ expect: fails 'compileJava' output.contains(logStatement()) failure.assertHasErrorOutput("cannot find symbol") - failure.assertHasErrorOutput("class Optional") + failure.assertHasErrorOutput("method takeWhile") } def buildScript() { diff --git a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy index c65ab74a4b5d7..0d469b996c868 100644 --- a/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy +++ b/subprojects/plugins/src/integTest/groovy/org/gradle/java/compile/IncrementalJavaCompileIntegrationTest.groovy @@ -37,7 +37,7 @@ class IncrementalJavaCompileIntegrationTest extends AbstractIntegrationSpec impl executedAndNotSkipped ':compileJava' when: - buildFile << 'sourceCompatibility = 1.6\n' + buildFile << 'sourceCompatibility = 1.8\n' succeeds ':compileJava' then: @@ -159,6 +159,34 @@ class IncrementalJavaCompileIntegrationTest extends AbstractIntegrationSpec impl false | true } + def "removes stale class file when file moves in hierarchy"() { + given: + file('src/main/java/IPerson.java') << basicInterface + buildFile << "apply plugin: 'java'\n" + + when: + succeeds 'classes' + + then: + executedAndNotSkipped ':compileJava' + file('build/classes/java/main/IPerson.class').exists() + !file('build/classes/java/main/some/package/IPerson.class').exists() + + when: + file('src/main/java/some/loc/IPerson.java') << """ + package some.loc; + """ << basicInterface + assert file('src/main/java/IPerson.java').delete() + + and: + succeeds 'assemble' + + then: + executedAndNotSkipped ':compileJava' + !file('build/classes/java/main/IPerson.class').exists() + file('build/classes/java/main/some/loc/IPerson.class').exists() + } + private static String getBasicInterface() { ''' interface IPerson { diff --git a/subprojects/plugins/src/main/java/org/gradle/api/internal/java/usagecontext/ConfigurationVariantMapping.java b/subprojects/plugins/src/main/java/org/gradle/api/internal/java/usagecontext/ConfigurationVariantMapping.java index a59c63d1abb30..4fc3fc698fca4 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/internal/java/usagecontext/ConfigurationVariantMapping.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/internal/java/usagecontext/ConfigurationVariantMapping.java @@ -23,22 +23,22 @@ import org.gradle.api.InvalidUserDataException; import org.gradle.api.NamedDomainObjectContainer; import org.gradle.api.artifacts.ConfigurablePublishArtifact; -import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationVariant; import org.gradle.api.artifacts.PublishArtifactSet; import org.gradle.api.attributes.AttributeContainer; import org.gradle.api.capabilities.Capability; import org.gradle.api.component.ConfigurationVariantDetails; +import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal; import org.gradle.api.internal.component.UsageContext; import java.util.Collection; import java.util.Set; public class ConfigurationVariantMapping { - private final Configuration outgoingConfiguration; + private final ConfigurationInternal outgoingConfiguration; private final Action action; - public ConfigurationVariantMapping(Configuration outgoingConfiguration, Action action) { + public ConfigurationVariantMapping(ConfigurationInternal outgoingConfiguration, Action action) { this.outgoingConfiguration = outgoingConfiguration; this.action = action; } @@ -109,6 +109,7 @@ public ConfigurationVariant attributes(Action action @Override public AttributeContainer getAttributes() { + outgoingConfiguration.preventFromFurtherMutation(); return outgoingConfiguration.getAttributes(); } } @@ -134,9 +135,13 @@ public void skip() { } @Override - public void mapToMavenScope(String scope, boolean optional) { + public void mapToOptional() { + this.optional = true; + } + + @Override + public void mapToMavenScope(String scope) { this.mavenScope = assertValidScope(scope); - this.optional = optional; } private static String assertValidScope(String scope) { diff --git a/subprojects/plugins/src/main/java/org/gradle/api/internal/java/usagecontext/FeatureConfigurationUsageContext.java b/subprojects/plugins/src/main/java/org/gradle/api/internal/java/usagecontext/FeatureConfigurationUsageContext.java index 1b188db7ccd5d..6b49840f66652 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/internal/java/usagecontext/FeatureConfigurationUsageContext.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/internal/java/usagecontext/FeatureConfigurationUsageContext.java @@ -18,17 +18,20 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationVariant; import org.gradle.api.internal.attributes.AttributeContainerInternal; +import org.gradle.api.internal.component.IvyPublishingAwareContext; import org.gradle.api.internal.component.MavenPublishingAwareContext; import org.gradle.api.plugins.internal.AbstractConfigurationUsageContext; -public class FeatureConfigurationUsageContext extends AbstractConfigurationUsageContext implements MavenPublishingAwareContext { +public class FeatureConfigurationUsageContext extends AbstractConfigurationUsageContext implements MavenPublishingAwareContext, IvyPublishingAwareContext { private final Configuration configuration; private final ScopeMapping scopeMapping; + private final boolean optional; public FeatureConfigurationUsageContext(String name, Configuration configuration, ConfigurationVariant variant, String mavenScope, boolean optional) { super(name, ((AttributeContainerInternal)variant.getAttributes()).asImmutable(), variant.getArtifacts()); this.configuration = configuration; this.scopeMapping = ScopeMapping.of(mavenScope, optional); + this.optional = optional; } @Override @@ -40,4 +43,9 @@ protected Configuration getConfiguration() { public ScopeMapping getScopeMapping() { return scopeMapping; } + + @Override + public boolean isOptional() { + return optional; + } } diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/BasePlugin.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/BasePlugin.java index c3577dd3c2952..4f0203071cb06 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/BasePlugin.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/BasePlugin.java @@ -103,14 +103,14 @@ public String call() { } task.getDestinationDirectory().convention(project.getLayout().getBuildDirectory().dir(project.provider(destinationDir))); - task.getArchiveVersion().set(project.provider(new Callable() { + task.getArchiveVersion().convention(project.provider(new Callable() { @Nullable public String call() { return project.getVersion() == Project.DEFAULT_VERSION ? null : project.getVersion().toString(); } })); - task.getArchiveBaseName().set(project.provider(new Callable() { + task.getArchiveBaseName().convention(project.provider(new Callable() { public String call() { return pluginConvention.getArchivesBaseName(); } diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/GroovyBasePlugin.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/GroovyBasePlugin.java index f126608368b09..0f171091180ad 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/GroovyBasePlugin.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/GroovyBasePlugin.java @@ -21,6 +21,7 @@ import org.gradle.api.Project; import org.gradle.api.Task; import org.gradle.api.Transformer; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.file.FileCollection; import org.gradle.api.file.FileTreeElement; @@ -29,6 +30,7 @@ import org.gradle.api.internal.tasks.DefaultGroovySourceSet; import org.gradle.api.internal.tasks.DefaultSourceSet; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.internal.JavaPluginsHelper; import org.gradle.api.plugins.internal.SourceSetUtil; import org.gradle.api.provider.Provider; import org.gradle.api.reporting.ReportingExtension; @@ -106,7 +108,7 @@ public boolean isSatisfiedBy(FileTreeElement element) { final Provider compileTask = project.getTasks().register(sourceSet.getCompileTaskName("groovy"), GroovyCompile.class, new Action() { @Override - public void execute(GroovyCompile compile) { + public void execute(final GroovyCompile compile) { SourceSetUtil.configureForSourceSet(sourceSet, groovySourceSet.getGroovy(), compile, compile.getOptions(), project); compile.dependsOn(sourceSet.getCompileJavaTaskName()); compile.setDescription("Compiles the " + sourceSet.getName() + " Groovy source."); @@ -120,6 +122,18 @@ public CompileOptions transform(GroovyCompile groovyCompile) { } })); + if (SourceSet.MAIN_SOURCE_SET_NAME.equals(sourceSet.getName())) { + // The user has chosen to use the java-library plugin; + // add a classes variant for groovy's main sourceSet compilation, + // so default-configuration project dependencies will see groovy's output. + project.getPluginManager().withPlugin("java-library", new Action() { + @Override + public void execute(AppliedPlugin plugin) { + Configuration apiElements = project.getConfigurations().getByName(sourceSet.getApiElementsConfigurationName()); + JavaPluginsHelper.registerClassesDirVariant(compileTask, project.getObjects(), apiElements); + } + }); + } // TODO: `classes` should be a little more tied to the classesDirs for a SourceSet so every plugin // doesn't need to do this. diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaBasePlugin.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaBasePlugin.java index 8d64e4b06cbb0..526b6673798a8 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaBasePlugin.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaBasePlugin.java @@ -32,8 +32,9 @@ import org.gradle.api.file.SourceDirectorySet; import org.gradle.api.internal.ConventionMapping; import org.gradle.api.internal.IConventionAware; -import org.gradle.api.internal.artifacts.dsl.ComponentMetadataHandlerInternal; import org.gradle.api.internal.artifacts.JavaEcosystemSupport; +import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal; +import org.gradle.api.internal.artifacts.dsl.ComponentMetadataHandlerInternal; import org.gradle.api.internal.plugins.DslObject; import org.gradle.api.internal.project.ProjectInternal; import org.gradle.api.model.ObjectFactory; @@ -150,7 +151,7 @@ public void execute(final SourceSet sourceSet) { ConfigurationContainer configurations = project.getConfigurations(); - defineConfigurationsForSourceSet(sourceSet, configurations); + defineConfigurationsForSourceSet(sourceSet, configurations, pluginConvention); definePathsForSourceSet(sourceSet, outputConventionMapping, project); createProcessResourcesTask(sourceSet, sourceSet.getResources(), project); @@ -231,7 +232,7 @@ public Object call() { sourceSet.getResources().srcDir("src/" + sourceSet.getName() + "/resources"); } - private void defineConfigurationsForSourceSet(SourceSet sourceSet, ConfigurationContainer configurations) { + private void defineConfigurationsForSourceSet(SourceSet sourceSet, ConfigurationContainer configurations, final JavaPluginConvention convention) { String compileConfigurationName = sourceSet.getCompileConfigurationName(); String implementationConfigurationName = sourceSet.getImplementationConfigurationName(); String runtimeConfigurationName = sourceSet.getRuntimeConfigurationName(); @@ -241,6 +242,8 @@ private void defineConfigurationsForSourceSet(SourceSet sourceSet, Configuration String annotationProcessorConfigurationName = sourceSet.getAnnotationProcessorConfigurationName(); String runtimeClasspathConfigurationName = sourceSet.getRuntimeClasspathConfigurationName(); String sourceSetName = sourceSet.toString(); + Action configureDefaultTargetPlatform = configureDefaultTargetPlatform(convention); + Configuration compileConfiguration = configurations.maybeCreate(compileConfigurationName); compileConfiguration.setVisible(false); @@ -269,6 +272,7 @@ private void defineConfigurationsForSourceSet(SourceSet sourceSet, Configuration compileClasspathConfiguration.setCanBeConsumed(false); compileClasspathConfiguration.getAttributes().attribute(USAGE_ATTRIBUTE, objectFactory.named(Usage.class, Usage.JAVA_API)); compileClasspathConfiguration.getAttributes().attribute(BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.class, Bundling.EXTERNAL)); + ((ConfigurationInternal)compileClasspathConfiguration).beforeLocking(configureDefaultTargetPlatform); Configuration annotationProcessorConfiguration = configurations.maybeCreate(annotationProcessorConfigurationName); annotationProcessorConfiguration.setVisible(false); @@ -290,12 +294,24 @@ private void defineConfigurationsForSourceSet(SourceSet sourceSet, Configuration runtimeClasspathConfiguration.extendsFrom(runtimeOnlyConfiguration, runtimeConfiguration, implementationConfiguration); runtimeClasspathConfiguration.getAttributes().attribute(USAGE_ATTRIBUTE, objectFactory.named(Usage.class, Usage.JAVA_RUNTIME)); runtimeClasspathConfiguration.getAttributes().attribute(BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.class, Bundling.EXTERNAL)); + ((ConfigurationInternal)runtimeClasspathConfiguration).beforeLocking(configureDefaultTargetPlatform); sourceSet.setCompileClasspath(compileClasspathConfiguration); sourceSet.setRuntimeClasspath(sourceSet.getOutput().plus(runtimeClasspathConfiguration)); sourceSet.setAnnotationProcessorPath(annotationProcessorConfiguration); } + private Action configureDefaultTargetPlatform(final JavaPluginConvention convention) { + return new Action() { + @Override + public void execute(ConfigurationInternal conf) { + if (!convention.getAutoTargetJvmDisabled()) { + JavaEcosystemSupport.configureDefaultTargetPlatform(conf, convention.getTargetCompatibility()); + } + } + }; + } + private void configureCompileDefaults(final Project project, final JavaPluginConvention javaConvention) { project.getTasks().withType(AbstractCompile.class).configureEach(new Action() { public void execute(final AbstractCompile compile) { diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlatformPlugin.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlatformPlugin.java index 67d8761042009..c435e14657e03 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlatformPlugin.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlatformPlugin.java @@ -23,12 +23,14 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; +import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.SoftwareComponentFactory; import org.gradle.api.internal.artifacts.JavaEcosystemSupport; import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport; import org.gradle.api.internal.java.DefaultJavaPlatformExtension; +import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.internal.JavaConfigurationVariantMapping; import javax.inject.Inject; @@ -93,17 +95,24 @@ public JavaPlatformPlugin(SoftwareComponentFactory softwareComponentFactory) { @Override public void apply(Project project) { + if (project.getPluginManager().hasPlugin("java")) { + // This already throws when creating `apiElements` so be eager to have a clear error message + throw new IllegalStateException("The \"java-platform\" plugin cannot be applied together with the \"java\" (or \"java-library\") plugin. " + + "A project is either a platform or a library but cannot be both at the same time."); + } project.getPluginManager().apply(BasePlugin.class); createConfigurations(project); configureExtension(project); addPlatformDisambiguationRule(project); JavaEcosystemSupport.configureSchema(project.getDependencies().getAttributesSchema(), project.getObjects()); + + } private void addPlatformDisambiguationRule(Project project) { project.getDependencies() .getAttributesSchema() - .getMatchingStrategy(PlatformSupport.COMPONENT_CATEGORY) + .getMatchingStrategy(Category.CATEGORY_ATTRIBUTE) .getDisambiguationRules() .add(PlatformSupport.PreferRegularPlatform.class); } @@ -118,18 +127,18 @@ private void createSoftwareComponent(Project project, Configuration apiElements, private void createConfigurations(Project project) { ConfigurationContainer configurations = project.getConfigurations(); Configuration api = configurations.create(API_CONFIGURATION_NAME, AS_BUCKET); - Configuration apiElements = createConsumableApi(project, configurations, api, API_ELEMENTS_CONFIGURATION_NAME, PlatformSupport.REGULAR_PLATFORM); - Configuration enforcedApiElements = createConsumableApi(project, configurations, api, ENFORCED_API_ELEMENTS_CONFIGURATION_NAME, PlatformSupport.ENFORCED_PLATFORM); + Configuration apiElements = createConsumableApi(project, configurations, api, API_ELEMENTS_CONFIGURATION_NAME, Category.REGULAR_PLATFORM); + Configuration enforcedApiElements = createConsumableApi(project, configurations, api, ENFORCED_API_ELEMENTS_CONFIGURATION_NAME, Category.ENFORCED_PLATFORM); Configuration runtime = project.getConfigurations().create(RUNTIME_CONFIGURATION_NAME, AS_BUCKET); runtime.extendsFrom(api); - Configuration runtimeElements = createConsumableRuntime(project, runtime, RUNTIME_ELEMENTS_CONFIGURATION_NAME, PlatformSupport.REGULAR_PLATFORM); - Configuration enforcedRuntimeElements = createConsumableRuntime(project, runtime, ENFORCED_RUNTIME_ELEMENTS_CONFIGURATION_NAME, PlatformSupport.ENFORCED_PLATFORM); + Configuration runtimeElements = createConsumableRuntime(project, runtime, RUNTIME_ELEMENTS_CONFIGURATION_NAME, Category.REGULAR_PLATFORM); + Configuration enforcedRuntimeElements = createConsumableRuntime(project, runtime, ENFORCED_RUNTIME_ELEMENTS_CONFIGURATION_NAME, Category.ENFORCED_PLATFORM); Configuration classpath = configurations.create(CLASSPATH_CONFIGURATION_NAME, AS_RESOLVABLE_CONFIGURATION); classpath.extendsFrom(runtimeElements); - declareConfigurationUsage(project, classpath, Usage.JAVA_RUNTIME); + declareConfigurationUsage(project.getObjects(), classpath, Usage.JAVA_RUNTIME); createSoftwareComponent(project, apiElements, runtimeElements); } @@ -137,25 +146,25 @@ private void createConfigurations(Project project) { private Configuration createConsumableRuntime(Project project, Configuration apiElements, String name, String platformKind) { Configuration runtimeElements = project.getConfigurations().create(name, AS_CONSUMABLE_CONFIGURATION); runtimeElements.extendsFrom(apiElements); - declareConfigurationUsage(project, runtimeElements, Usage.JAVA_RUNTIME); - declareConfigurationCategory(runtimeElements, platformKind); + declareConfigurationUsage(project.getObjects(), runtimeElements, Usage.JAVA_RUNTIME); + declareConfigurationCategory(project.getObjects(), runtimeElements, platformKind); return runtimeElements; } private Configuration createConsumableApi(Project project, ConfigurationContainer configurations, Configuration api, String name, String platformKind) { Configuration apiElements = configurations.create(name, AS_CONSUMABLE_CONFIGURATION); apiElements.extendsFrom(api); - declareConfigurationUsage(project, apiElements, Usage.JAVA_API); - declareConfigurationCategory(apiElements, platformKind); + declareConfigurationUsage(project.getObjects(), apiElements, Usage.JAVA_API); + declareConfigurationCategory(project.getObjects(), apiElements, platformKind); return apiElements; } - private void declareConfigurationCategory(Configuration configuration, String value) { - configuration.getAttributes().attribute(PlatformSupport.COMPONENT_CATEGORY, value); + private void declareConfigurationCategory(ObjectFactory objectFactory, Configuration configuration, String value) { + configuration.getAttributes().attribute(Category.CATEGORY_ATTRIBUTE, objectFactory.named(Category.class, value)); } - private void declareConfigurationUsage(Project project, Configuration configuration, String usage) { - configuration.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, usage)); + private void declareConfigurationUsage(ObjectFactory objectFactory, Configuration configuration, String usage) { + configuration.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, objectFactory.named(Usage.class, usage)); } private void configureExtension(Project project) { diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlugin.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlugin.java index e8ab4dc69c96b..cb0c79ebc383d 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlugin.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPlugin.java @@ -29,12 +29,15 @@ import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.PublishArtifact; import org.gradle.api.artifacts.type.ArtifactTypeDefinition; +import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.attributes.Bundling; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.SoftwareComponentFactory; import org.gradle.api.file.FileCollection; import org.gradle.api.internal.artifacts.ArtifactAttributes; +import org.gradle.api.internal.artifacts.JavaEcosystemSupport; +import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal; import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact; import org.gradle.api.internal.component.BuildableJavaComponent; import org.gradle.api.internal.component.ComponentRegistry; @@ -53,6 +56,7 @@ import org.gradle.api.tasks.testing.Test; import org.gradle.internal.cleanup.BuildOutputCleanupRegistry; import org.gradle.language.jvm.tasks.ProcessResources; +import org.gradle.util.DeprecationLogger; import javax.inject.Inject; import java.io.File; @@ -63,6 +67,7 @@ import static org.gradle.api.attributes.Usage.USAGE_ATTRIBUTE; import static org.gradle.api.attributes.Bundling.EXTERNAL; import static org.gradle.api.attributes.Bundling.BUNDLING_ATTRIBUTE; +import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE; /** *

    A {@link Plugin} which compiles and tests Java source, and assembles it into a JAR file.

    @@ -116,6 +121,7 @@ public class JavaPlugin implements Plugin { /** * The name of the API configuration, where dependencies exported by a component at compile time should * be declared. + * * @since 3.4 */ public static final String API_CONFIGURATION_NAME = "api"; @@ -123,6 +129,7 @@ public class JavaPlugin implements Plugin { /** * The name of the implementation configuration, where dependencies that are only used internally by * a component should be declared. + * * @since 3.4 */ public static final String IMPLEMENTATION_CONFIGURATION_NAME = "implementation"; @@ -160,12 +167,14 @@ public class JavaPlugin implements Plugin { /** * The name of the runtime only dependencies configuration, used to declare dependencies * that should only be found at runtime. + * * @since 3.4 */ public static final String RUNTIME_ONLY_CONFIGURATION_NAME = "runtimeOnly"; /** * The name of the runtime classpath configuration, used by a component to query its own runtime classpath. + * * @since 3.4 */ public static final String RUNTIME_CLASSPATH_CONFIGURATION_NAME = "runtimeClasspath"; @@ -173,18 +182,21 @@ public class JavaPlugin implements Plugin { /** * The name of the runtime elements configuration, that should be used by consumers * to query the runtime dependencies of a component. + * * @since 3.4 */ public static final String RUNTIME_ELEMENTS_CONFIGURATION_NAME = "runtimeElements"; /** * The name of the compile classpath configuration. + * * @since 3.4 */ public static final String COMPILE_CLASSPATH_CONFIGURATION_NAME = "compileClasspath"; /** * The name of the annotation processor configuration. + * * @since 4.6 */ @Incubating @@ -194,6 +206,7 @@ public class JavaPlugin implements Plugin { /** * The name of the test implementation dependencies configuration. + * * @since 3.4 */ public static final String TEST_IMPLEMENTATION_CONFIGURATION_NAME = "testImplementation"; @@ -214,18 +227,21 @@ public class JavaPlugin implements Plugin { /** * The name of the test runtime only dependencies configuration. + * * @since 3.4 */ public static final String TEST_RUNTIME_ONLY_CONFIGURATION_NAME = "testRuntimeOnly"; /** * The name of the test compile classpath configuration. + * * @since 3.4 */ public static final String TEST_COMPILE_CLASSPATH_CONFIGURATION_NAME = "testCompileClasspath"; /** * The name of the test annotation processor configuration. + * * @since 4.6 */ @Incubating @@ -233,6 +249,7 @@ public class JavaPlugin implements Plugin { /** * The name of the test runtime classpath configuration. + * * @since 3.4 */ public static final String TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME = "testRuntimeClasspath"; @@ -254,12 +271,17 @@ public void apply(ProjectInternal project) { BuildOutputCleanupRegistry buildOutputCleanupRegistry = project.getServices().get(BuildOutputCleanupRegistry.class); configureSourceSets(javaConvention, buildOutputCleanupRegistry); - configureConfigurations(project); + configureConfigurations(project, javaConvention); configureJavaDoc(javaConvention); configureTest(project, javaConvention); configureArchivesAndComponent(project, javaConvention); configureBuild(project); + + if (project.getPluginManager().hasPlugin("java-platform")) { + DeprecationLogger.nagUserOfDeprecatedBehaviour("The \"java\" (or \"java-library\") plugin cannot be used together with the \"java-platform\" plugin on a given project. " + + "A project is either a platform or a library but cannot be both at the same time."); + } } private void configureSourceSets(JavaPluginConvention pluginConvention, final BuildOutputCleanupRegistry buildOutputCleanupRegistry) { @@ -371,14 +393,14 @@ private void configureBuild(Project project) { @Override public void execute(Task task) { addDependsOnTaskInOtherProjects(task, true, - JavaBasePlugin.BUILD_NEEDED_TASK_NAME, TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME); + JavaBasePlugin.BUILD_NEEDED_TASK_NAME, TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME); } }); project.getTasks().named(JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME, new Action() { @Override public void execute(Task task) { addDependsOnTaskInOtherProjects(task, false, - JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME, TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME); + JavaBasePlugin.BUILD_DEPENDENTS_TASK_NAME, TEST_RUNTIME_CLASSPATH_CONFIGURATION_NAME); } }); } @@ -414,7 +436,7 @@ public void execute(Task task) { }); } - private void configureConfigurations(Project project) { + private void configureConfigurations(Project project, final JavaPluginConvention convention) { ConfigurationContainer configurations = project.getConfigurations(); Configuration defaultConfiguration = configurations.getByName(Dependency.DEFAULT_CONFIGURATION); @@ -432,25 +454,43 @@ private void configureConfigurations(Project project) { testRuntimeConfiguration.extendsFrom(runtimeConfiguration); testRuntimeOnlyConfiguration.extendsFrom(runtimeOnlyConfiguration); - Configuration apiElementsConfiguration = configurations.maybeCreate(API_ELEMENTS_CONFIGURATION_NAME); + final Configuration apiElementsConfiguration = configurations.maybeCreate(API_ELEMENTS_CONFIGURATION_NAME); apiElementsConfiguration.setVisible(false); apiElementsConfiguration.setDescription("API elements for main."); apiElementsConfiguration.setCanBeResolved(false); apiElementsConfiguration.setCanBeConsumed(true); apiElementsConfiguration.getAttributes().attribute(USAGE_ATTRIBUTE, objectFactory.named(Usage.class, Usage.JAVA_API_JARS)); apiElementsConfiguration.getAttributes().attribute(BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.class, EXTERNAL)); + apiElementsConfiguration.getAttributes().attribute(CATEGORY_ATTRIBUTE, objectFactory.named(Category.class, Category.LIBRARY)); apiElementsConfiguration.extendsFrom(runtimeConfiguration); - Configuration runtimeElementsConfiguration = configurations.maybeCreate(RUNTIME_ELEMENTS_CONFIGURATION_NAME); + final Configuration runtimeElementsConfiguration = configurations.maybeCreate(RUNTIME_ELEMENTS_CONFIGURATION_NAME); runtimeElementsConfiguration.setVisible(false); runtimeElementsConfiguration.setCanBeConsumed(true); runtimeElementsConfiguration.setCanBeResolved(false); runtimeElementsConfiguration.setDescription("Elements of runtime for main."); runtimeElementsConfiguration.getAttributes().attribute(USAGE_ATTRIBUTE, objectFactory.named(Usage.class, Usage.JAVA_RUNTIME_JARS)); runtimeElementsConfiguration.getAttributes().attribute(BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.class, EXTERNAL)); + runtimeElementsConfiguration.getAttributes().attribute(CATEGORY_ATTRIBUTE, objectFactory.named(Category.class, Category.LIBRARY)); runtimeElementsConfiguration.extendsFrom(implementationConfiguration, runtimeOnlyConfiguration, runtimeConfiguration); defaultConfiguration.extendsFrom(runtimeElementsConfiguration); + + + configureTargetPlatform(apiElementsConfiguration, convention); + configureTargetPlatform(runtimeElementsConfiguration, convention); + } + + /** + * Configures the target platform for an outgoing configuration. + */ + private void configureTargetPlatform(Configuration outgoing, final JavaPluginConvention convention) { + ((ConfigurationInternal)outgoing).beforeLocking(new Action() { + @Override + public void execute(ConfigurationInternal configuration) { + JavaEcosystemSupport.configureDefaultTargetPlatform(configuration, convention.getTargetCompatibility()); + } + }); } /** diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPluginConvention.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPluginConvention.java index 2e26f43c657af..a602dcda665ac 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPluginConvention.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPluginConvention.java @@ -18,6 +18,7 @@ import groovy.lang.Closure; import org.gradle.api.Action; +import org.gradle.api.Incubating; import org.gradle.api.JavaVersion; import org.gradle.api.internal.project.ProjectInternal; import org.gradle.api.java.archives.Manifest; @@ -157,4 +158,27 @@ public abstract class JavaPluginConvention { public abstract SourceSetContainer getSourceSets(); public abstract ProjectInternal getProject(); + + /** + * If this method is called, Gradle will not automatically try to fetch + * dependencies which have a JVM version compatible with this module. + * This should be used whenever the default behavior is not + * applicable, in particular when for some reason it's not possible to split + * a module and that this module only has some classes which require dependencies + * on higher versions. + * + * @since 5.3 + */ + @Incubating + public abstract void disableAutoTargetJvm(); + + /** + * Tells if automatic JVM targetting is enabled. When disabled, Gradle + * will not automatically try to get dependencies corresponding to the + * same (or compatible) level as the target compatibility of this module. + * + * @since 5.3 + */ + @Incubating + public abstract boolean getAutoTargetJvmDisabled(); } diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPluginExtension.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPluginExtension.java index 6c6179ad5e6be..2f53d0ee63326 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPluginExtension.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/JavaPluginExtension.java @@ -59,4 +59,17 @@ public interface JavaPluginExtension { * @since 5.3 */ void registerFeature(String name, Action configureAction); + + /** + * If this method is called, Gradle will not automatically try to fetch + * dependencies which have a JVM version compatible with the target compatibility + * of this module. This should be used whenever the default behavior is not + * applicable, in particular when for some reason it's not possible to split + * a module and that this module only has some classes which require dependencies + * on higher versions. + * + * @since 5.3 + */ + @Incubating + void disableAutoTargetJvm(); } diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultAdhocSoftwareComponent.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultAdhocSoftwareComponent.java index a9f08068439f6..60b874beea4f3 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultAdhocSoftwareComponent.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultAdhocSoftwareComponent.java @@ -21,6 +21,7 @@ import org.gradle.api.artifacts.Configuration; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.ConfigurationVariantDetails; +import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal; import org.gradle.api.internal.component.SoftwareComponentInternal; import org.gradle.api.internal.component.UsageContext; import org.gradle.api.internal.java.usagecontext.ConfigurationVariantMapping; @@ -43,7 +44,7 @@ public String getName() { @Override public void addVariantsFromConfiguration(Configuration outgoingConfiguration, Action spec) { - variants.add(new ConfigurationVariantMapping(outgoingConfiguration, spec)); + variants.add(new ConfigurationVariantMapping((ConfigurationInternal) outgoingConfiguration, spec)); } @Override diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaFeatureSpec.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaFeatureSpec.java index 75ce66a48d4ad..dd84355e1ccf9 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaFeatureSpec.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaFeatureSpec.java @@ -17,17 +17,22 @@ import com.google.common.collect.Lists; import org.gradle.api.Action; +import org.gradle.api.InvalidUserCodeException; import org.gradle.api.Task; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ConfigurationContainer; import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.attributes.Bundling; +import org.gradle.api.attributes.java.TargetJvmVersion; import org.gradle.api.capabilities.Capability; import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.SoftwareComponent; import org.gradle.api.component.SoftwareComponentContainer; +import org.gradle.api.internal.artifacts.configurations.ConfigurationInternal; import org.gradle.api.internal.artifacts.dsl.LazyPublishArtifact; +import org.gradle.api.internal.attributes.AttributeContainerInternal; import org.gradle.api.model.ObjectFactory; import org.gradle.api.plugins.AppliedPlugin; import org.gradle.api.plugins.BasePlugin; @@ -47,6 +52,8 @@ import static org.gradle.api.attributes.Bundling.EXTERNAL; import static org.gradle.api.attributes.Bundling.BUNDLING_ATTRIBUTE; +import static org.gradle.api.attributes.Category.LIBRARY; +import static org.gradle.api.attributes.Category.CATEGORY_ATTRIBUTE; import static org.gradle.api.plugins.internal.JavaPluginsHelper.registerClassesDirVariant; public class DefaultJavaFeatureSpec implements FeatureSpecInternal { @@ -100,6 +107,9 @@ public void create() { } private void setupConfigurations(SourceSet sourceSet) { + if (sourceSet == null) { + throw new InvalidUserCodeException("You must specify which source set to use for feature '" + name + "'"); + } String apiConfigurationName; String implConfigurationName; String apiElementsConfigurationName; @@ -127,6 +137,10 @@ private void setupConfigurations(SourceSet sourceSet) { configureUsage(runtimeElements, Usage.JAVA_RUNTIME_JARS); configurePacking(apiElements); configurePacking(runtimeElements); + configureTargetPlatform(apiElements); + configureTargetPlatform(runtimeElements); + configureCategory(apiElements); + configureCategory(runtimeElements); configureCapabilities(apiElements); configureCapabilities(runtimeElements); attachArtifactToConfiguration(apiElements); @@ -154,10 +168,28 @@ public void execute(AppliedPlugin plugin) { }); } + private void configureTargetPlatform(Configuration configuration) { + ((ConfigurationInternal)configuration).beforeLocking(new Action() { + @Override + public void execute(ConfigurationInternal configuration) { + String majorVersion = javaPluginConvention.getTargetCompatibility().getMajorVersion(); + AttributeContainerInternal attributes = configuration.getAttributes(); + // If nobody said anything about this variant's target platform, use whatever the convention says + if (!attributes.contains(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE)) { + attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, Integer.valueOf(majorVersion)); + } + } + }); + } + private void configurePacking(Configuration configuration) { configuration.getAttributes().attribute(BUNDLING_ATTRIBUTE, objectFactory.named(Bundling.class, EXTERNAL)); } + private void configureCategory(Configuration configuration) { + configuration.getAttributes().attribute(CATEGORY_ATTRIBUTE, objectFactory.named(Category.class, LIBRARY)); + } + private void attachArtifactToConfiguration(Configuration configuration) { String jarTaskName = sourceSet.getJarTaskName(); if (!tasks.getNames().contains(jarTaskName)) { diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaPluginConvention.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaPluginConvention.java index 6c561c1a4e0df..91e0335c1a46f 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaPluginConvention.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaPluginConvention.java @@ -52,6 +52,8 @@ public class DefaultJavaPluginConvention extends JavaPluginConvention implements private JavaVersion srcCompat; private JavaVersion targetCompat; + private boolean autoTargetJvm = true; + public DefaultJavaPluginConvention(ProjectInternal project, ObjectFactory objectFactory) { this.project = project; sourceSets = objectFactory.newInstance(DefaultSourceSetContainer.class); @@ -179,4 +181,14 @@ public SourceSetContainer getSourceSets() { public ProjectInternal getProject() { return project; } + + @Override + public void disableAutoTargetJvm() { + this.autoTargetJvm = false; + } + + @Override + public boolean getAutoTargetJvmDisabled() { + return !autoTargetJvm; + } } diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaPluginExtension.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaPluginExtension.java index 7eda00231853d..28d7b2e7cc7fb 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaPluginExtension.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/DefaultJavaPluginExtension.java @@ -89,6 +89,11 @@ public void registerFeature(String name, Action configureAc spec.create(); } + @Override + public void disableAutoTargetJvm() { + convention.disableAutoTargetJvm(); + } + private static String validateFeatureName(String name) { if (!VALID_FEATURE_NAME.matcher(name).matches()) { throw new InvalidUserDataException("Invalid feature name '" + name + "'. Must match " + VALID_FEATURE_NAME.pattern()); diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JavaConfigurationVariantMapping.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JavaConfigurationVariantMapping.java index 669db74d8c9bc..4dbda22097486 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JavaConfigurationVariantMapping.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JavaConfigurationVariantMapping.java @@ -37,7 +37,10 @@ public JavaConfigurationVariantMapping(String scope, boolean optional) { public void execute(ConfigurationVariantDetails details) { ConfigurationVariant variant = details.getConfigurationVariant(); if (ArtifactTypeSpec.INSTANCE.isSatisfiedBy(variant)) { - details.mapToMavenScope(scope, optional); + details.mapToMavenScope(scope); + if (optional) { + details.mapToOptional(); + } } else { details.skip(); } diff --git a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JavaPluginsHelper.java b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JavaPluginsHelper.java index e2fd9d2e04a70..66061fd46654f 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JavaPluginsHelper.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/plugins/internal/JavaPluginsHelper.java @@ -23,7 +23,7 @@ import org.gradle.api.internal.artifacts.publish.AbstractPublishArtifact; import org.gradle.api.model.ObjectFactory; import org.gradle.api.provider.Provider; -import org.gradle.api.tasks.compile.JavaCompile; +import org.gradle.api.tasks.compile.AbstractCompile; import javax.annotation.Nullable; import java.io.File; @@ -34,15 +34,15 @@ * into the public API. */ public class JavaPluginsHelper { - public static void registerClassesDirVariant(final Provider javaCompile, ObjectFactory objectFactory, Configuration configuration) { + public static void registerClassesDirVariant(final Provider compileTask, ObjectFactory objectFactory, Configuration configuration) { // Define a classes variant to use for compilation ConfigurationPublications publications = configuration.getOutgoing(); - ConfigurationVariant variant = publications.getVariants().create("classes"); + ConfigurationVariant variant = publications.getVariants().maybeCreate("classes"); variant.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, objectFactory.named(Usage.class, Usage.JAVA_API_CLASSES)); - variant.artifact(new IntermediateJavaArtifact(ArtifactTypeDefinition.JVM_CLASS_DIRECTORY, javaCompile) { + variant.artifact(new IntermediateJavaArtifact(ArtifactTypeDefinition.JVM_CLASS_DIRECTORY, compileTask) { @Override public File getFile() { - return javaCompile.get().getDestinationDir(); + return compileTask.get().getDestinationDir(); } }); } diff --git a/subprojects/plugins/src/main/java/org/gradle/api/tasks/SourceSet.java b/subprojects/plugins/src/main/java/org/gradle/api/tasks/SourceSet.java index a815a6595a44a..4bf60ce178c8a 100644 --- a/subprojects/plugins/src/main/java/org/gradle/api/tasks/SourceSet.java +++ b/subprojects/plugins/src/main/java/org/gradle/api/tasks/SourceSet.java @@ -25,10 +25,13 @@ import javax.annotation.Nullable; /** - * A {@code SourceSet} represents a logical group of Java source and resources. + * A {@code SourceSet} represents a logical group of Java source and resource files. They + * are covered in more detail in the + * user manual. *

    - * See the example below how {@link SourceSet} 'main' is accessed and how the {@link SourceDirectorySet} 'java' - * is configured to exclude some package from compilation. + * The following example shows how you can configure the 'main' source set, which in this + * case involves excluding classes whose package begins 'some.unwatned.package' from + * compilation of the source files in the 'java' {@link SourceDirectorySet}: * *

      * apply plugin: 'java'
    diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
    index e3314e95f0c09..96a5d2d0eba29 100644
    --- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
    +++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaBasePluginTest.groovy
    @@ -18,6 +18,7 @@ package org.gradle.api.plugins
     import org.gradle.api.DefaultTask
     import org.gradle.api.JavaVersion
     import org.gradle.api.attributes.CompatibilityCheckDetails
    +import org.gradle.api.attributes.MultipleCandidatesDetails
     import org.gradle.api.attributes.Usage
     import org.gradle.api.internal.artifacts.JavaEcosystemSupport
     import org.gradle.api.reporting.ReportingExtension
    @@ -37,7 +38,9 @@ import org.gradle.platform.base.BinarySpec
     import org.gradle.test.fixtures.AbstractProjectBuilderSpec
     import org.gradle.test.fixtures.file.TestFile
     import org.gradle.util.SetSystemProperties
    +import org.gradle.util.TestUtil
     import org.junit.Rule
    +import spock.lang.Issue
     import spock.lang.Unroll
     
     import static org.gradle.api.file.FileCollectionMatchers.sameCollection
    @@ -500,4 +503,43 @@ class JavaBasePluginTest extends AbstractProjectBuilderSpec {
             Usage.JAVA_RUNTIME_JARS      | Usage.JAVA_RUNTIME_RESOURCES | false
             Usage.JAVA_RUNTIME_JARS      | Usage.JAVA_RUNTIME_JARS      | true
         }
    +
    +    @Issue("gradle/gradle#8700")
    +    @Unroll
    +    def "check default disambiguation rules (consumer=#consumer, candidates=#candidates, selected=#preferred)"() {
    +        given:
    +        JavaEcosystemSupport.UsageDisambiguationRules rules = new JavaEcosystemSupport.UsageDisambiguationRules(
    +                usage(Usage.JAVA_API),
    +                usage(Usage.JAVA_API_JARS),
    +                usage(Usage.JAVA_API_CLASSES),
    +                usage(Usage.JAVA_RUNTIME),
    +                usage(Usage.JAVA_RUNTIME_JARS),
    +                usage(Usage.JAVA_RUNTIME_CLASSES),
    +                usage(Usage.JAVA_RUNTIME_RESOURCES)
    +        )
    +        MultipleCandidatesDetails details = Mock()
    +
    +        when:
    +        rules.execute(details)
    +
    +        then:
    +        1 * details.getConsumerValue() >> usage(consumer)
    +        1 * details.getCandidateValues() >> candidates.collect { usage(it) }
    +        1 * details.closestMatch(usage(preferred))
    +
    +        where: // not exhaustive, tests pathological cases
    +        consumer                | candidates                                            | preferred
    +        Usage.JAVA_API          | [Usage.JAVA_API_JARS, Usage.JAVA_API_CLASSES]         | Usage.JAVA_API_CLASSES
    +        Usage.JAVA_API          | [Usage.JAVA_RUNTIME_CLASSES, Usage.JAVA_API_CLASSES]  | Usage.JAVA_API_CLASSES
    +        Usage.JAVA_API          | [Usage.JAVA_RUNTIME_CLASSES, Usage.JAVA_RUNTIME_JARS] | Usage.JAVA_RUNTIME_CLASSES
    +        Usage.JAVA_API          | [Usage.JAVA_API_JARS, Usage.JAVA_RUNTIME_JARS]        | Usage.JAVA_API_JARS
    +
    +        Usage.JAVA_RUNTIME      | [Usage.JAVA_RUNTIME, Usage.JAVA_RUNTIME_JARS]         | Usage.JAVA_RUNTIME
    +        Usage.JAVA_RUNTIME_JARS | [Usage.JAVA_RUNTIME, Usage.JAVA_RUNTIME_JARS]         | Usage.JAVA_RUNTIME_JARS
    +
    +    }
    +
    +    private Usage usage(String value) {
    +        TestUtil.objectFactory().named(Usage, value)
    +    }
     }
    diff --git a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPlatformPluginTest.groovy b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPlatformPluginTest.groovy
    index 6782599c045bc..25d186c60039c 100644
    --- a/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPlatformPluginTest.groovy
    +++ b/subprojects/plugins/src/test/groovy/org/gradle/api/plugins/JavaPlatformPluginTest.groovy
    @@ -18,8 +18,8 @@ package org.gradle.api.plugins
     
     import org.gradle.api.ProjectConfigurationException
     import org.gradle.api.artifacts.ModuleDependency
    +import org.gradle.api.attributes.Category
     import org.gradle.api.attributes.Usage
    -import org.gradle.api.internal.artifacts.dsl.dependencies.PlatformSupport
     import org.gradle.api.internal.component.UsageContext
     import org.gradle.test.fixtures.AbstractProjectBuilderSpec
     import spock.lang.Unroll
    @@ -120,17 +120,17 @@ class JavaPlatformPluginTest extends AbstractProjectBuilderSpec {
             runtimeUsage.dependencies == project.configurations.getByName(JavaPlatformPlugin.RUNTIME_CONFIGURATION_NAME).allDependencies.withType(ModuleDependency)
             runtimeUsage.dependencyConstraints.size() == 2
             runtimeUsage.dependencyConstraints == project.configurations.getByName(JavaPlatformPlugin.RUNTIME_CONFIGURATION_NAME).allDependencyConstraints
    -        runtimeUsage.attributes.keySet() == [Usage.USAGE_ATTRIBUTE, PlatformSupport.COMPONENT_CATEGORY] as Set
    +        runtimeUsage.attributes.keySet() == [Usage.USAGE_ATTRIBUTE, Category.CATEGORY_ATTRIBUTE] as Set
             runtimeUsage.attributes.getAttribute(Usage.USAGE_ATTRIBUTE).name == Usage.JAVA_RUNTIME
    -        runtimeUsage.attributes.getAttribute(PlatformSupport.COMPONENT_CATEGORY) == PlatformSupport.REGULAR_PLATFORM
    +        runtimeUsage.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE).name == Category.REGULAR_PLATFORM
     
             apiUsage.dependencies.size() == 1
             apiUsage.dependencies == project.configurations.getByName(JavaPlatformPlugin.API_CONFIGURATION_NAME).allDependencies.withType(ModuleDependency)
             apiUsage.dependencyConstraints.size() == 1
             apiUsage.dependencyConstraints == project.configurations.getByName(JavaPlatformPlugin.API_CONFIGURATION_NAME).allDependencyConstraints
    -        apiUsage.attributes.keySet() == [Usage.USAGE_ATTRIBUTE, PlatformSupport.COMPONENT_CATEGORY] as Set
    +        apiUsage.attributes.keySet() == [Usage.USAGE_ATTRIBUTE, Category.CATEGORY_ATTRIBUTE] as Set
             apiUsage.attributes.getAttribute(Usage.USAGE_ATTRIBUTE).name == Usage.JAVA_API
    -        apiUsage.attributes.getAttribute(PlatformSupport.COMPONENT_CATEGORY) == PlatformSupport.REGULAR_PLATFORM
    +        apiUsage.attributes.getAttribute(Category.CATEGORY_ATTRIBUTE).name == Category.REGULAR_PLATFORM
         }
     
         @Unroll("cannot add a dependency to the #configuration configuration by default")
    diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/ModuleMetadataFileGenerator.java b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/GradleModuleMetadataWriter.java
    similarity index 97%
    rename from subprojects/publish/src/main/java/org/gradle/api/publish/internal/ModuleMetadataFileGenerator.java
    rename to subprojects/publish/src/main/java/org/gradle/api/publish/internal/GradleModuleMetadataWriter.java
    index 416ddd7554dca..14e3f5a33dea4 100644
    --- a/subprojects/publish/src/main/java/org/gradle/api/publish/internal/ModuleMetadataFileGenerator.java
    +++ b/subprojects/publish/src/main/java/org/gradle/api/publish/internal/GradleModuleMetadataWriter.java
    @@ -38,7 +38,7 @@
     import org.gradle.api.component.SoftwareComponent;
     import org.gradle.api.internal.artifacts.DefaultExcludeRule;
     import org.gradle.api.internal.artifacts.dependencies.DefaultImmutableVersionConstraint;
    -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser;
    +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser;
     import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyPublicationResolver;
     import org.gradle.api.internal.attributes.AttributeContainerInternal;
     import org.gradle.api.internal.attributes.ImmutableAttributes;
    @@ -69,17 +69,17 @@
      * 

    Whenever you change this class, make sure you also:

    * *
      - *
    • Update the corresponding {@link ModuleMetadataParser module metadata parser}
    • + *
    • Update the corresponding {@link GradleModuleMetadataParser module metadata parser}
    • *
    • Update the module metadata specification (subprojects/docs/src/docs/design/gradle-module-metadata-specification.md)
    • *
    • Update {@link org.gradle.api.internal.artifacts.ivyservice.modulecache.ModuleMetadataSerializer the module metadata serializer}
    • *
    • Add a sample for the module metadata serializer test, to make sure that serialized metadata is idempotent
    • *
    */ -public class ModuleMetadataFileGenerator { +public class GradleModuleMetadataWriter { private final BuildInvocationScopeId buildInvocationScopeId; private final ProjectDependencyPublicationResolver projectDependencyResolver; - public ModuleMetadataFileGenerator(BuildInvocationScopeId buildInvocationScopeId, ProjectDependencyPublicationResolver projectDependencyResolver) { + public GradleModuleMetadataWriter(BuildInvocationScopeId buildInvocationScopeId, ProjectDependencyPublicationResolver projectDependencyResolver) { this.buildInvocationScopeId = buildInvocationScopeId; this.projectDependencyResolver = projectDependencyResolver; } @@ -257,7 +257,7 @@ private void writeCreator(JsonWriter jsonWriter) throws IOException { private void writeFormat(JsonWriter jsonWriter) throws IOException { jsonWriter.name("formatVersion"); - jsonWriter.value(ModuleMetadataParser.FORMAT_VERSION); + jsonWriter.value(GradleModuleMetadataParser.FORMAT_VERSION); } private void writeVariantHostedInAnotherModule(ModuleVersionIdentifier coordinates, ModuleVersionIdentifier targetCoordinates, UsageContext variant, JsonWriter jsonWriter) throws IOException { @@ -328,6 +328,9 @@ private void writeAttributes(AttributeContainer attributes, JsonWriter jsonWrite if (value instanceof Boolean) { Boolean b = (Boolean) value; jsonWriter.value(b); + } else if (value instanceof Integer) { + Integer i = (Integer) value; + jsonWriter.value(i); } else if (value instanceof String) { String s = (String) value; jsonWriter.value(s); diff --git a/subprojects/publish/src/main/java/org/gradle/api/publish/tasks/GenerateModuleMetadata.java b/subprojects/publish/src/main/java/org/gradle/api/publish/tasks/GenerateModuleMetadata.java index aba31839eb3d5..48b54f90fcbe0 100644 --- a/subprojects/publish/src/main/java/org/gradle/api/publish/tasks/GenerateModuleMetadata.java +++ b/subprojects/publish/src/main/java/org/gradle/api/publish/tasks/GenerateModuleMetadata.java @@ -35,7 +35,7 @@ import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.publish.Publication; -import org.gradle.api.publish.internal.ModuleMetadataFileGenerator; +import org.gradle.api.publish.internal.GradleModuleMetadataWriter; import org.gradle.api.publish.internal.PublicationInternal; import org.gradle.api.specs.Spec; import org.gradle.api.specs.Specs; @@ -167,7 +167,7 @@ void run() { try { Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), "utf8")); try { - new ModuleMetadataFileGenerator(getBuildInvocationScopeId(), getProjectDependencyPublicationResolver()).generateTo(publication, publications, writer); + new GradleModuleMetadataWriter(getBuildInvocationScopeId(), getProjectDependencyPublicationResolver()).generateTo(publication, publications, writer); } finally { writer.close(); } diff --git a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/ModuleMetadataFileGeneratorTest.groovy b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/ModuleMetadataFileGeneratorTest.groovy index fc8560884fd5d..1e51f1452e903 100644 --- a/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/ModuleMetadataFileGeneratorTest.groovy +++ b/subprojects/publish/src/test/groovy/org/gradle/api/publish/internal/ModuleMetadataFileGeneratorTest.groovy @@ -28,7 +28,7 @@ import org.gradle.api.component.ComponentWithVariants import org.gradle.api.internal.artifacts.DefaultExcludeRule import org.gradle.api.internal.artifacts.DefaultModuleVersionIdentifier import org.gradle.api.internal.artifacts.dependencies.DefaultImmutableVersionConstraint -import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.ModuleMetadataParser +import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.parser.GradleModuleMetadataParser import org.gradle.api.internal.artifacts.ivyservice.projectmodule.ProjectDependencyPublicationResolver import org.gradle.api.internal.attributes.ImmutableAttributes import org.gradle.api.internal.component.SoftwareComponentInternal @@ -64,7 +64,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { def buildId = UniqueId.generate() def id = DefaultModuleVersionIdentifier.newId("group", "module", "1.2") def projectDependencyResolver = Mock(ProjectDependencyPublicationResolver) - def generator = new ModuleMetadataFileGenerator(new BuildInvocationScopeId(buildId), projectDependencyResolver) + def generator = new GradleModuleMetadataWriter(new BuildInvocationScopeId(buildId), projectDependencyResolver) def "writes file for component with no variants"() { def writer = new StringWriter() @@ -76,7 +76,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "group": "group", "module": "module", @@ -104,7 +104,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "group": "group", "module": "module", @@ -162,7 +162,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "group": "group", "module": "module", @@ -292,7 +292,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "group": "group", "module": "module", @@ -463,7 +463,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "group": "group", "module": "module", @@ -559,7 +559,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "group": "group", "module": "module", @@ -619,7 +619,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "group": "group", "module": "module", @@ -684,7 +684,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "url": "../../module/1.2/module-1.2.module", "group": "group", @@ -745,7 +745,7 @@ class ModuleMetadataFileGeneratorTest extends Specification { then: writer.toString() == """{ - "formatVersion": "${ModuleMetadataParser.FORMAT_VERSION}", + "formatVersion": "${GradleModuleMetadataParser.FORMAT_VERSION}", "component": { "group": "group", "module": "module", diff --git a/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/DefaultSslContextFactory.java b/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/DefaultSslContextFactory.java index e62e34c691a5b..f8435f11df258 100644 --- a/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/DefaultSslContextFactory.java +++ b/subprojects/resources-http/src/main/java/org/gradle/internal/resource/transport/http/DefaultSslContextFactory.java @@ -48,10 +48,13 @@ public class DefaultSslContextFactory implements SslContextFactory { "javax.net.ssl.keyStoreType", "javax.net.ssl.keyStore", "javax.net.ssl.keyStoreProvider", - "javax.net.ssl.keyStorePassword" + "javax.net.ssl.keyStorePassword", + "java.home" ); - private LoadingCache, SSLContext> cache = CacheBuilder.newBuilder().softValues().build(new SslContextCacheLoader()); + private LoadingCache, SSLContext> cache = CacheBuilder.newBuilder().softValues().build( + new SynchronizedSystemPropertiesCacheLoader(new SslContextCacheLoader()) + ); @Override public SSLContext createSslContext() { @@ -66,15 +69,46 @@ public Map create() { for (String prop : SSL_SYSTEM_PROPERTIES) { currentProperties.put(prop, System.getProperty(prop)); } - currentProperties.put("java.home", SystemProperties.getInstance().getJavaHomeDir().getPath()); return currentProperties; } }); } + private static class SynchronizedSystemPropertiesCacheLoader extends CacheLoader, SSLContext> { + private final SslContextCacheLoader delegate; + + private SynchronizedSystemPropertiesCacheLoader(SslContextCacheLoader delegate) { + this.delegate = delegate; + } + + @Override + public SSLContext load(Map props) { + /* + * NOTE! The JDK code to create SSLContexts relies on the values of the given system properties. + * + * To prevent concurrent changes to system properties from interfering with this, we need to synchronize access/modifications + * to system properties. This is best effort since we can't prevent user code from modifying system properties willy-nilly. + * + * The most critical system property is java.home. Changing this property while trying to create a SSLContext can cause many strange + * problems: + * https://github.com/gradle/gradle/issues/8830 + * https://github.com/gradle/gradle/issues/8039 + * https://github.com/gradle/gradle/issues/7842 + * https://github.com/gradle/gradle/issues/2588 + */ + return SystemProperties.getInstance().withSystemProperties(props, new Factory() { + @Override + public SSLContext create() { + return delegate.load(props); + } + }); + } + } + private static class SslContextCacheLoader extends CacheLoader, SSLContext> { @Override public SSLContext load(Map props) { + // TODO: We should see if we can go back to using HttpClient again. // This implementation is borrowed from the Apache HttpClient project // https://github.com/apache/httpclient/blob/4.2.2/httpclient/src/main/java/org/apache/http/conn/ssl/SSLSocketFactory.java#L246-L354 try { diff --git a/subprojects/resources-http/src/test/groovy/org/gradle/internal/resource/transport/http/DefaultSslContextFactoryTest.groovy b/subprojects/resources-http/src/test/groovy/org/gradle/internal/resource/transport/http/DefaultSslContextFactoryTest.groovy index f15acfa4ef4b6..8fb49376e7f40 100644 --- a/subprojects/resources-http/src/test/groovy/org/gradle/internal/resource/transport/http/DefaultSslContextFactoryTest.groovy +++ b/subprojects/resources-http/src/test/groovy/org/gradle/internal/resource/transport/http/DefaultSslContextFactoryTest.groovy @@ -17,11 +17,7 @@ package org.gradle.internal.resource.transport.http import org.apache.http.ssl.SSLInitializationException - -import org.gradle.internal.SystemProperties - import spock.lang.Specification - /** * Tests loading of keystores and truststores corresponding to system * properties specified. @@ -31,7 +27,7 @@ class DefaultSslContextFactoryTest extends Specification { def loader void setup() { - props = ['java.home': SystemProperties.getInstance().javaHomeDir.path] + props = ['java.home': System.properties['java.home']] loader = new DefaultSslContextFactory.SslContextCacheLoader() } diff --git a/subprojects/resources-sftp/resources-sftp.gradle.kts b/subprojects/resources-sftp/resources-sftp.gradle.kts index 9fd58fa5fec3d..4c78b80f14ef0 100644 --- a/subprojects/resources-sftp/resources-sftp.gradle.kts +++ b/subprojects/resources-sftp/resources-sftp.gradle.kts @@ -29,7 +29,6 @@ dependencies { implementation(library("slf4j_api")) implementation(library("guava")) implementation(library("jsch")) - implementation(library("jcip")) implementation(library("commons_io")) } diff --git a/subprojects/resources-sftp/src/main/java/org/gradle/internal/resource/transport/sftp/SftpClientFactory.java b/subprojects/resources-sftp/src/main/java/org/gradle/internal/resource/transport/sftp/SftpClientFactory.java index f554e82ac27c0..6a0ea29ba2240 100644 --- a/subprojects/resources-sftp/src/main/java/org/gradle/internal/resource/transport/sftp/SftpClientFactory.java +++ b/subprojects/resources-sftp/src/main/java/org/gradle/internal/resource/transport/sftp/SftpClientFactory.java @@ -20,7 +20,7 @@ import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; import com.jcraft.jsch.*; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.api.artifacts.repositories.PasswordCredentials; import org.gradle.api.resources.ResourceException; import org.gradle.internal.concurrent.CompositeStoppable; diff --git a/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaCrossCompilationIntegrationTest.groovy b/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaCrossCompilationIntegrationTest.groovy index 2eabe6bd475c9..a009336a3473d 100644 --- a/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaCrossCompilationIntegrationTest.groovy +++ b/subprojects/scala/src/integTest/groovy/org/gradle/scala/ScalaCrossCompilationIntegrationTest.groovy @@ -25,6 +25,8 @@ import org.gradle.test.fixtures.file.ClassFile import org.gradle.util.TextUtil import org.junit.Assume +import static org.gradle.util.TestPrecondition.SUPPORTS_TARGETING_JAVA6 + @TargetVersions(["1.6", "1.7", "1.8"]) class ScalaCrossCompilationIntegrationTest extends MultiVersionIntegrationSpec { JavaVersion getJavaVersion() { @@ -39,6 +41,7 @@ class ScalaCrossCompilationIntegrationTest extends MultiVersionIntegrationSpec { Assume.assumeTrue(target != null) // Java Compiler does not fork for joint compilation - therefore we cannot compile for a Java Version bigger than the current JVM Assume.assumeTrue(javaVersion.compareTo(JavaVersion.current()) <= 0) + Assume.assumeFalse(javaVersion.java6 && !SUPPORTS_TARGETING_JAVA6.fulfilled) def java = TextUtil.escapeString(target.getJavaExecutable()) def javaHome = TextUtil.escapeString(target.javaHome.absolutePath) diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/InMemoryPgpSignatoryProviderIntegrationSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/InMemoryPgpSignatoryProviderIntegrationSpec.groovy new file mode 100644 index 0000000000000..ae1bc883e3241 --- /dev/null +++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/InMemoryPgpSignatoryProviderIntegrationSpec.groovy @@ -0,0 +1,206 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.plugins.signing + +class InMemoryPgpSignatoryProviderIntegrationSpec extends SigningIntegrationSpec { + + def "signs with default signatory"() { + given: + buildFile << """ + signing { + useInMemoryPgpKeys(project.properties['secretKey'], project.properties['password']) + sign(jar) + } + """ + + when: + executer.withEnvironmentVars([ + ORG_GRADLE_PROJECT_secretKey: secretKeyWithPassword, + ORG_GRADLE_PROJECT_password: password + ]) + succeeds("signJar") + + then: + executed(":signJar") + file("build", "libs", "sign-1.0.jar.asc").exists() + } + + def "signs with custom signatory"() { + given: + buildFile << """ + signing { + useInMemoryPgpKeys('foo', 'bar') + signatories { + custom(project.properties['secretKey'], project.properties['password']) + } + sign(jar)*.signatory = signatories.custom + } + """ + + when: + executer.withEnvironmentVars([ + ORG_GRADLE_PROJECT_secretKey: secretKeyWithPassword, + ORG_GRADLE_PROJECT_password: password + ]) + succeeds("signJar") + + then: + executed(":signJar") + file("build", "libs", "sign-1.0.jar.asc").exists() + } + + def "supports keys without passwords"() { + given: + buildFile << """ + signing { + useInMemoryPgpKeys(project.properties['secretKey'], '') + sign(jar) + } + """ + + when: + executer.withEnvironmentVars([ + ORG_GRADLE_PROJECT_secretKey: secretKeyWithoutPassword + ]) + succeeds("signJar") + + then: + executed(":signJar") + file("build", "libs", "sign-1.0.jar.asc").exists() + } + + final secretKeyWithPassword = '''\ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQPGBFxb6KEBCAC/lBOqM5Qx116XOWIK3vavHF3eSNx9PbCtGZCRiYeB0xbGvKPw +mSg4j2YMxpxOazdeD24KNExvR5EGUdI/4LTqZLiF/o37sY/GDbYdgSrKo99DCbqC +DsX6loXe9tJQGMFXMhm+ILy+YzmzGZD+4JGxn4Dro8zndIkKUP1OgTEUNEl5Y03c +lcpYPg60o57RkFUqSCAw+Pr4w18nyI6yVw3eX48M1fpf7YAsLURtRjKRbWuDumae +YC5zRpK7fYMkCMTEwP0yzvvGPRusFD7BNORWItjAv3O0sGwctAsF1GWCS9aLAD3E +piYYS4UBFgqiAblYokWNmlwbfqmA4SDM4HAFABEBAAH+BwMCsGT03PkwWrPp7rqS +DjnWsKW5dO6L2jT9jNK6kg1/HcvKQWN2olm/PvJrOcLAZ2qX1qxWQhxuq5NubTk8 +h8zUN5weshuqj/9hUsITSb0YyJEb+sXnUU3NTiPZZZlKaLeYyXCWInNk8SOk7mph +pBTv+4JIe2Z3H3Rfkp+UcjCn6+CSry7zLxmb1jYwBlITZwzU55EYl5k6Q48uFwIZ +FD/oSatolB97pC1JzrxV+LTqDicfHYuIjhcFKVhMtUW9ZUbN9jgn1H/kXbkK6zog +CAkkpydTNZZtF/mTy1K0x57KlL097RDGLxOfEuHFXPq+cluRJRlS/sNXfLmswOk7 +p5aHTXst2/NzzL69xc6sgFzDIuJgi48s5QTzqF+VM5k1LjO8qLONIC7c5wveuZsg +BP7OawBYknpXuZtjwRBShp2/JUPtUAjtU8tLHWUrYdd2+0lqtBTGMWEYvzUr3DuH +vaV6Bg5/jAXMDvv4besse799IM8B/BLO5zuzE195+MBGeKyR8k5tgvI7UEmdgOUA +b/lWRldVaCICTLa7BYyjGDmuw7yF/bzYk5kUmdKKnUd3QcyqVr3czJ4meso4YD57 +PQY2JbXXIp33kWaWvAFuSv8g/aCytj/7L4ImIE6kbO1QWlxUQ3pq/XuaRKzqEO07 +ZkoWm0B6fbjh3DrcHAIfdi11H5lgBuYyRUpgJigL4OO04schmI0hfnFhv/T5Ovnd +tu56h7ToP/PpqlnP11IxRk1Nf9pgYezDKq8agLy+2x66si171lzxSzf4mXzg80CH +z8ldMDtBR2G9+4PhguqvygTLHSbBGuu7STzvsQaN2hmtM+RWj1gAch4HlQ2XhZ6y +fDA/fbG2tJnUvEdBoK6uhjSHcct9M79iyaNrxYWCu2PJcIK0ayEFytWNLO0SSmoj +lUeEGJLQ8kahtBlGb28gQmFyIDxmb29AZXhhbXBsZS5jb20+iQFUBBMBCAA+FiEE +QtS6pb0ASq/RhzRI/Urkj6otOZIFAlxb6KECGwMFCQPCZwAFCwkIBwIGFQoJCAsC +BBYCAwECHgECF4AACgkQ/Urkj6otOZJHhAf+MrWpiWDGsu9yW0lbln7FQpXvhNim +mO4aykYcLRnttr4fbbTaTiq1S+Vn/5/zmcfkGnjdCM5RCO/nZA1mcXpg8pmd+emX +SS72owHVDq1we2QD+/WQljUDY4Qdf0AxqPQm+ARGWC2RwgNA6CSH3Q72fE2por5H +FXti3kjq79NRt8OG+iUZ7W00/v//wzVkQw4m3iTjy2G1Ih8tPEkxEjKoNTfXUNMP +TIHLAdo5/mwj/4M1aK3DeSQkdJtkK2RUTUghrOTZus1Gu+5jJjCbjJp7W1Gl5qsZ ++hS68rh4FgGxdo3V8e/dKuXMff0eKEaf8qter8V+32V2esMXr8rJKWT5j50DxgRc +W+ihAQgAwfN8rOSnX0fZ7bbzGulXW8D5qv+y4w5Mel7Us+2jBOB5sGNWikw0agIv +CSjpGnKX2Ucr9uQET46LIH1P1ZV/kxGNme2VEgntVwKn2OcVAQugsPaqpgVihw0S +cUyFtQ/UP1x5SrUk8TNyzI2hXfa1s7qRDl30gsEI3T7bOQEa7vgZcDCYx7dT5YRG +6KpfuoMli7LAA0YqH8aDzAV1bLCEJTgcV1+CsQ9oR0VRXp3EwIBuGvhF5lsUnHXR +lq3G0yY54fysgyOj6+/bzZZiAj2qlJ62nLi7IpQpvwirgkNlHZ4GfyON5hYC0H7M +FSlcvMjcYUxKtwHS5TGmEW1/0/O9xwARAQAB/gcDAn5HnUC9rymY6fiMOrqhGtmF +IPn2VD5paHYHbqgY70hRe+7ufgEPHVLW4AOvcwCX75+iOB9rIclLoXyiX/dRtwlE +Cl8GKGIDP6gRmsDwReROYg+kMyFieFLXZ7pETxgkwJP98ZQwSDNpxsMltdXlvY42 +DrbnZfMPY35Zf+81QKDFqbUSTaZSQpEgNUhkBxPXA68bEvKU565/n7mRRp8tuu2q +PSTzU6uw7NQJEtxiVPswmO2E6n7nZ7E19K3JVW6BYhV+IhFBwN2z72cWUYncJaTa +c/BPPr+WGzTgO1wAwk6+T2wILuAo+nCKQ1RjUCZ4ViRKIv06Aq5LotW+tn572pL9 +Enj/8cZ91YunAAleoInVNOrc6JfIxDtdkZjFhTC+0AA2zH8N4YNfY3CUAQWNEOab +Ysn4On15JZypVAqtWMyCkcWm2pBdMOk2iLiITyZCd7o2wTjz43JzdXE9GFQCBcw1 +ZzN3zPa47/ymRqqYSUKNFYkfhWI6+Hk6mfATBJWiMF8UNv8D1JNPX6S7UM/ZGFGq +tZdXqlayrlC6XBETs6GmQFfyTWRsSqxuax4k3Z+FNoqGUEqwGanw+fob4tfV9xaC +1wN55KbEz47qinBR1f103clRxwneZM/KgSckRF5KzP4hSTgtl+iVZZKqDjvxzenU +1z8/APp8vh64bUaqDXnWui98edgitdYNT/XXUXDBgfDrtbAC+NGP015FMuBc0Mxh +ygMxrdBn3gMKGHGq7T3SdDuc2YC56bQxDdoBbfiG9MtfdOGljjJzr3o3ALgmSj6s +NE3rTIoDXQKhpXMTJdTDPHgcsY6Cjrb7Q92gIuZ8tf3zDvA14ttIkTc/luJIlheu +tWc0Jy0gxbrjSuv5L3iXiG/Abdo3r31dzg7rE5LQK5zR1a8gwUaPHLXrtqPl1Dy/ +y1QVmokBPAQYAQgAJhYhBELUuqW9AEqv0Yc0SP1K5I+qLTmSBQJcW+ihAhsMBQkD +wmcAAAoJEP1K5I+qLTmS3KwH/RXvVUpChQ+c4AnoBiJfiHfrHM18kil2sl8e+Yah +6Q0m0UX3G5kyWkDNK/mIrcrLFj0SSKNudMNwF7XHUZ+NDe+B+i+0BG7mH/VjbTt4 +NJqM6mfUyEECsPtbNXbCyboi06M/BJLnFI05HYIXI3LwnBKrg3Hicd8L+RXujMGH +UtgJrAsMmQXaKEc1oYMq/dgjGfpfbOc2O5Y72dCjMzj5LQkWtw/yEMWFOmPT4YVD +7t3qmbZy2RRhhqtELIYyPYFdz4JuOnNdRkanosYsKdjMssnV6+4YLcOcX7s5VZvY +ZZ8X/eH+PzjPrhshJR+f4lP7gh1k34mWtw9vlnvhQEdUQw8= +=AXAR +-----END PGP PRIVATE KEY BLOCK-----''' + final password = 'foo' + + final secretKeyWithoutPassword = '''\ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQOYBFyI758BCACn1dO3pid06f4lGcRrLxEVmVi4jNgJgSAuUlciMV6QiIuM8VZ5 +bq6F04XJDJZgnqFOXlrIK83Rf+MwhIfgu20zCS+E7CsEX1uVE7k+9rN90sA9sPrE +q0TKZFwjWzOrpc1IGwxa+NOUTweiYIRyXV88cqB0xpux+WDU7W1CrqO6FgpXD8f7 +emWbcB/E47KJSN5L9YeQrb65wqhspIfucHTZLaUO5Afl0mlR1mLT2B2ejaRhAWJX +4jXLNGZChCnSGkBfgSu6akCYwrh8quO17s0iKzDF+dNbWji18JDYRAxOj01EM7V/ +CHNYTmRHnor8BiDhSDWL4ztI4E5xcaU8HvWvABEBAAEAB/0eZz3TJuY+56SCVAig +4gXWQ9EunVUFY77QpVnjd84JoLKm9ZEUrlgvJgI2SXF0T0gpSi5n1IeUS/Z784Yp +z8oYVLGnAqFISX3to4ULQuWBBYyNoGHM/rmXcFbAkOTrUz28simq0SiC1U4svA9C +KGf4K0ul29SYiPRhniEM01YVf11Ya42weOCrXIHFNSvIUEuSg05GWOWP3i/Y5CSx +VZvfPJyuxvMMYbGQ0WJCEkbnkm3EqYMdMqCCHyH3fTS4aKt0Q3HTT18mJhlLUP35 +VYBcrIxDWmqgIlbwwS9AQU/XQzsNz0N067G/m4zeo6Cw16y8Kx1quvHu9D+HabPD +NlsBBADBYeI1+qFpe5EuVGG4MWeMnW0iWtu7pquuX6UYFTODS+n1n2aNUPlHmeaZ +SBevsVFwisueVhwNEA90kNS9C9c+RkK65hUzgxhLyAFgXucGTnoFFh2s+wZk2Wih +7i+jKOOBLqcllNpKS1C1gLYmm+05ExsFmcwbL349YYmI87AwAwQA3i5CI69IZkwX +7qR0gENDkGnmONchzF+aFojFhtirwMzosJHpxhFLuG9rlK8wtdJ3xlCIZrVm/B80 +ugEOYqXjAtrR+ad0ZPGr0RL5WxiCDCCyW6VdktP4772mS1jRIYtiQma/8R5LsA1k +6u9DfXcIZacFTdX2xByzLXJhqMUcAeUD/0JpnESj/cj2WsAj4QSOg0qMv1v0A0AQ +0E11tTFu1FsMpVCV9AQx5h3bUbZeDiO00/0o70w17iHciz0nI3si375c0gHsSUyj +paS/5c86gaeFHrTEr0UA1PJqZIHo+tieZ9Gvn8beHKaVDWZZTtnSCCkHc2dGy30J +NDpQLhY28BLwOw+0GVRlc3RpbmcgPHRlc3RAZ3JhZGxlLm9yZz6JAVQEEwEIAD4W +IQR+ADFmwHrj3vOohgOFqMMFox2drAUCXIjvnwIbAwUJA8JnAAULCQgHAgYVCgkI +CwIEFgIDAQIeAQIXgAAKCRCFqMMFox2drPFtB/9K80qF0bmqgT/9HT50oyP7SsdR +K6aof36xSSNpBGhTqSTCzA/u6TZPD9uT3nrBp77uFRAu6y1+Ry9sQt2UWf0R9HWK +PkSkfVF59hrRchBCqCw3HdREVbjQqCT0K/B0EHoOOoZ2IQbvNpvxEBjenENj4o7q +HjpL9LZWmhtJNy9EDDsE8WCMkIdzBng9TQTdPmvfKIjiwjmCtSL4e8MNDIGh8JpP +2p2fnS1DHTyCkbi3uBf65hr9zR+FDv22OhGRJPRv50Wvi4BL2sRusaiyrCubNydt +GXqE8dYOG8pAbNMTbBM7Ncj/wriDENxqDcOFIjXB4ghQMdlncxxKUVgXzPajnQOY +BFyI758BCAC0dNOZ+95havJEHLQMB5bA0wf0Owy4C2h24MAyHk+PKIUuEVsDr/nf +kLRAz+2XeEiitcqYybzDPgIknBgcZcVGaIWVdojHsawm4py/upjG1ZRh4dajjLct +bV9/uijBdIHLCrLeRhyibkMSD3NHebjdxBr72uORc9jC8r98jBLsSDlKC6ayjnPb +fQrc3ujfhpthzXaTv9eanB8uSbUUVJmYb2SxQ2KTyjFt7/UwzO708JKaMjud/4Iw +qhHmUfxeg+xtWkr0Wx/TseNbJfXWPfmA1LSgj1L/AnQ7KkDv8sjLAe3Wv7u3PqTf +irI+e1Jb13GpJ/bWVP+4nE35s7aBMlRjABEBAAEAB/4z6cXz7urPIKqYYJ+FNGuw +hiUsJA6pJZMEW+y+nkyp9PC3S4Pg4C+kmqbYXFjP8ekHcf/aC3MzwbNxH7yp8rcZ +ZblEQajgteK9/wQz/fS0gr3gmM0cGL+boHLQNlhCKwepxyak3gufyNOfrvUtcz11 +AtT2bkZ4UhjiIF5o8I0DDtmo5ThqXjtTrdQMbScs4mp9kjodf55DP7HDhgm7OPdJ +PorA8hhv8vkdg+JzPJzzg8gn+WxPRaAlcZ9j22qiQMF1hc0hnBih6LVHFol/278i +3TDsrrCXlYcW30rlW0+G1+1Jc4p8fC7R5BC8fko9wtNoIJ4G1FK6e60+ATtp4CqZ +BADCE+p8MdzvmIp7tbdo8bZQZy2n1qY2P9GEqLdzPnN5mBaU1DnaCn7D+ME1cal+ +FKkgT5fB4nZEB1ClgZ8GRvJZEKzdBEVwzkBvl4zsFdTkVGYBBG47BlSjl7X6z6Rt +OOaaycqoPPfQUCmrBCbN2yvcnfnEnkSOwBvLWf0qeQXTqQQA7ghTE3BJ6vFDv37M +zTI73orPY/ZGh8uPefN4e2CKBQLvmZPFNWG3Ahm2PAxbmvejNPKWWAWGjHJdB924 +ER/TaFQO9fsX+19XTDaHGZCm57frLyzDvs4kn5u6zZ2015R6DxsmKavQqsdE6Zec +/uyy7QMIeSxt+aWDILimbjnV7ysD/3QRHljpSCYOYO25hrifoER/VJH4RW5i6m5p +D6MBo3FkmIYy94oHdVXaBKqlXieZfaRwzMAmArcgldHYMxTIVX1HDeSd+ya/jAuy +lEZMI5hTXmnUSfPTdLRn265jXxiJNxirZjw/8CFnwVAGDP9jAAnGjc6cl5TJIocy +IcTP7/55RvKJATwEGAEIACYWIQR+ADFmwHrj3vOohgOFqMMFox2drAUCXIjvnwIb +DAUJA8JnAAAKCRCFqMMFox2drBE5B/kBbIQfNrHSqX8DLgpSUsFJVna3/Yp7jTEA +ZfZhbgTorqvCTNyaZs9BNi6jaZ/GdkEv7dt9WMtcdm76r6LTgjpEMFGXQRAyTq9Z +A/4Op5poa+dMHPRj7FvJTX792XRau5iAUTS9N1PeITF+kT1sU/BC5jqCi8dghwEe +5B9Pc6JbQLxVxBHOa8eSg3wi+jUeEHYzWQ9xH6O1PQ4fyAHT31P1VZqEvKB0i02C +5D76SJrUXr6cRi9JE/+/rJ34SM7oZdCVKSyoZbsHIbvdCoW6gRe6vHm6i/sGGAxC +t39qTMnx9LG1EdsZJ1KMUMau1/h15WVWUK10cK7IPSQ2vV+dThEj +=Y8Fu +-----END PGP PRIVATE KEY BLOCK-----''' + +} diff --git a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy index 931718db632c7..5a1a054aab96e 100644 --- a/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy +++ b/subprojects/signing/src/integTest/groovy/org/gradle/plugins/signing/SigningSamplesSpec.groovy @@ -138,7 +138,91 @@ class SigningSamplesSpec extends AbstractSampleIntegrationTest { dsl << ['groovy', 'kotlin'] } + @Unroll + @UsesSample('signing/in-memory') + def "uses in-memory PGP keys with dsl #dsl"() { + given: + def projectDir = sample.dir.file(dsl) + inDirectory(projectDir) + + when: + executer.withEnvironmentVars([ + ORG_GRADLE_PROJECT_signingKey: secretKey, + ORG_GRADLE_PROJECT_signingPassword: password + ]) + succeeds("signStuffZip") + + then: + projectDir.file('build/distributions/stuff.zip.asc').exists() + + where: + dsl << ['groovy', 'kotlin'] + } + MavenFileRepository repoFor(String dsl) { return maven(sample.dir.file("$dsl/build/repo")) } + + final secretKey = '''\ +-----BEGIN PGP PRIVATE KEY BLOCK----- + +lQPGBFxb6KEBCAC/lBOqM5Qx116XOWIK3vavHF3eSNx9PbCtGZCRiYeB0xbGvKPw +mSg4j2YMxpxOazdeD24KNExvR5EGUdI/4LTqZLiF/o37sY/GDbYdgSrKo99DCbqC +DsX6loXe9tJQGMFXMhm+ILy+YzmzGZD+4JGxn4Dro8zndIkKUP1OgTEUNEl5Y03c +lcpYPg60o57RkFUqSCAw+Pr4w18nyI6yVw3eX48M1fpf7YAsLURtRjKRbWuDumae +YC5zRpK7fYMkCMTEwP0yzvvGPRusFD7BNORWItjAv3O0sGwctAsF1GWCS9aLAD3E +piYYS4UBFgqiAblYokWNmlwbfqmA4SDM4HAFABEBAAH+BwMCsGT03PkwWrPp7rqS +DjnWsKW5dO6L2jT9jNK6kg1/HcvKQWN2olm/PvJrOcLAZ2qX1qxWQhxuq5NubTk8 +h8zUN5weshuqj/9hUsITSb0YyJEb+sXnUU3NTiPZZZlKaLeYyXCWInNk8SOk7mph +pBTv+4JIe2Z3H3Rfkp+UcjCn6+CSry7zLxmb1jYwBlITZwzU55EYl5k6Q48uFwIZ +FD/oSatolB97pC1JzrxV+LTqDicfHYuIjhcFKVhMtUW9ZUbN9jgn1H/kXbkK6zog +CAkkpydTNZZtF/mTy1K0x57KlL097RDGLxOfEuHFXPq+cluRJRlS/sNXfLmswOk7 +p5aHTXst2/NzzL69xc6sgFzDIuJgi48s5QTzqF+VM5k1LjO8qLONIC7c5wveuZsg +BP7OawBYknpXuZtjwRBShp2/JUPtUAjtU8tLHWUrYdd2+0lqtBTGMWEYvzUr3DuH +vaV6Bg5/jAXMDvv4besse799IM8B/BLO5zuzE195+MBGeKyR8k5tgvI7UEmdgOUA +b/lWRldVaCICTLa7BYyjGDmuw7yF/bzYk5kUmdKKnUd3QcyqVr3czJ4meso4YD57 +PQY2JbXXIp33kWaWvAFuSv8g/aCytj/7L4ImIE6kbO1QWlxUQ3pq/XuaRKzqEO07 +ZkoWm0B6fbjh3DrcHAIfdi11H5lgBuYyRUpgJigL4OO04schmI0hfnFhv/T5Ovnd +tu56h7ToP/PpqlnP11IxRk1Nf9pgYezDKq8agLy+2x66si171lzxSzf4mXzg80CH +z8ldMDtBR2G9+4PhguqvygTLHSbBGuu7STzvsQaN2hmtM+RWj1gAch4HlQ2XhZ6y +fDA/fbG2tJnUvEdBoK6uhjSHcct9M79iyaNrxYWCu2PJcIK0ayEFytWNLO0SSmoj +lUeEGJLQ8kahtBlGb28gQmFyIDxmb29AZXhhbXBsZS5jb20+iQFUBBMBCAA+FiEE +QtS6pb0ASq/RhzRI/Urkj6otOZIFAlxb6KECGwMFCQPCZwAFCwkIBwIGFQoJCAsC +BBYCAwECHgECF4AACgkQ/Urkj6otOZJHhAf+MrWpiWDGsu9yW0lbln7FQpXvhNim +mO4aykYcLRnttr4fbbTaTiq1S+Vn/5/zmcfkGnjdCM5RCO/nZA1mcXpg8pmd+emX +SS72owHVDq1we2QD+/WQljUDY4Qdf0AxqPQm+ARGWC2RwgNA6CSH3Q72fE2por5H +FXti3kjq79NRt8OG+iUZ7W00/v//wzVkQw4m3iTjy2G1Ih8tPEkxEjKoNTfXUNMP +TIHLAdo5/mwj/4M1aK3DeSQkdJtkK2RUTUghrOTZus1Gu+5jJjCbjJp7W1Gl5qsZ ++hS68rh4FgGxdo3V8e/dKuXMff0eKEaf8qter8V+32V2esMXr8rJKWT5j50DxgRc +W+ihAQgAwfN8rOSnX0fZ7bbzGulXW8D5qv+y4w5Mel7Us+2jBOB5sGNWikw0agIv +CSjpGnKX2Ucr9uQET46LIH1P1ZV/kxGNme2VEgntVwKn2OcVAQugsPaqpgVihw0S +cUyFtQ/UP1x5SrUk8TNyzI2hXfa1s7qRDl30gsEI3T7bOQEa7vgZcDCYx7dT5YRG +6KpfuoMli7LAA0YqH8aDzAV1bLCEJTgcV1+CsQ9oR0VRXp3EwIBuGvhF5lsUnHXR +lq3G0yY54fysgyOj6+/bzZZiAj2qlJ62nLi7IpQpvwirgkNlHZ4GfyON5hYC0H7M +FSlcvMjcYUxKtwHS5TGmEW1/0/O9xwARAQAB/gcDAn5HnUC9rymY6fiMOrqhGtmF +IPn2VD5paHYHbqgY70hRe+7ufgEPHVLW4AOvcwCX75+iOB9rIclLoXyiX/dRtwlE +Cl8GKGIDP6gRmsDwReROYg+kMyFieFLXZ7pETxgkwJP98ZQwSDNpxsMltdXlvY42 +DrbnZfMPY35Zf+81QKDFqbUSTaZSQpEgNUhkBxPXA68bEvKU565/n7mRRp8tuu2q +PSTzU6uw7NQJEtxiVPswmO2E6n7nZ7E19K3JVW6BYhV+IhFBwN2z72cWUYncJaTa +c/BPPr+WGzTgO1wAwk6+T2wILuAo+nCKQ1RjUCZ4ViRKIv06Aq5LotW+tn572pL9 +Enj/8cZ91YunAAleoInVNOrc6JfIxDtdkZjFhTC+0AA2zH8N4YNfY3CUAQWNEOab +Ysn4On15JZypVAqtWMyCkcWm2pBdMOk2iLiITyZCd7o2wTjz43JzdXE9GFQCBcw1 +ZzN3zPa47/ymRqqYSUKNFYkfhWI6+Hk6mfATBJWiMF8UNv8D1JNPX6S7UM/ZGFGq +tZdXqlayrlC6XBETs6GmQFfyTWRsSqxuax4k3Z+FNoqGUEqwGanw+fob4tfV9xaC +1wN55KbEz47qinBR1f103clRxwneZM/KgSckRF5KzP4hSTgtl+iVZZKqDjvxzenU +1z8/APp8vh64bUaqDXnWui98edgitdYNT/XXUXDBgfDrtbAC+NGP015FMuBc0Mxh +ygMxrdBn3gMKGHGq7T3SdDuc2YC56bQxDdoBbfiG9MtfdOGljjJzr3o3ALgmSj6s +NE3rTIoDXQKhpXMTJdTDPHgcsY6Cjrb7Q92gIuZ8tf3zDvA14ttIkTc/luJIlheu +tWc0Jy0gxbrjSuv5L3iXiG/Abdo3r31dzg7rE5LQK5zR1a8gwUaPHLXrtqPl1Dy/ +y1QVmokBPAQYAQgAJhYhBELUuqW9AEqv0Yc0SP1K5I+qLTmSBQJcW+ihAhsMBQkD +wmcAAAoJEP1K5I+qLTmS3KwH/RXvVUpChQ+c4AnoBiJfiHfrHM18kil2sl8e+Yah +6Q0m0UX3G5kyWkDNK/mIrcrLFj0SSKNudMNwF7XHUZ+NDe+B+i+0BG7mH/VjbTt4 +NJqM6mfUyEECsPtbNXbCyboi06M/BJLnFI05HYIXI3LwnBKrg3Hicd8L+RXujMGH +UtgJrAsMmQXaKEc1oYMq/dgjGfpfbOc2O5Y72dCjMzj5LQkWtw/yEMWFOmPT4YVD +7t3qmbZy2RRhhqtELIYyPYFdz4JuOnNdRkanosYsKdjMssnV6+4YLcOcX7s5VZvY +ZZ8X/eH+PzjPrhshJR+f4lP7gh1k34mWtw9vlnvhQEdUQw8= +=AXAR +-----END PGP PRIVATE KEY BLOCK-----''' + final password = 'foo' + } diff --git a/subprojects/signing/src/main/java/org/gradle/plugins/signing/SigningExtension.java b/subprojects/signing/src/main/java/org/gradle/plugins/signing/SigningExtension.java index 336c714f73fab..5e8306aa649a2 100644 --- a/subprojects/signing/src/main/java/org/gradle/plugins/signing/SigningExtension.java +++ b/subprojects/signing/src/main/java/org/gradle/plugins/signing/SigningExtension.java @@ -38,12 +38,14 @@ import org.gradle.plugins.signing.signatory.Signatory; import org.gradle.plugins.signing.signatory.SignatoryProvider; import org.gradle.plugins.signing.signatory.internal.gnupg.GnupgSignatoryProvider; +import org.gradle.plugins.signing.signatory.internal.pgp.InMemoryPgpSignatoryProvider; import org.gradle.plugins.signing.signatory.pgp.PgpSignatoryProvider; import org.gradle.plugins.signing.type.DefaultSignatureTypeProvider; import org.gradle.plugins.signing.type.SignatureType; import org.gradle.plugins.signing.type.SignatureTypeProvider; import org.gradle.util.DeferredUtil; +import javax.annotation.Nullable; import java.io.File; import java.util.ArrayList; import java.util.HashMap; @@ -232,6 +234,25 @@ public void useGpgCmd() { setSignatories(new GnupgSignatoryProvider()); } + /** + * Use the supplied ascii-armored in-memory PGP secret key and password + * instead of reading it from a keyring. + * + *
    
    +     * signing {
    +     *     def secretKey = findProperty("mySigningKey")
    +     *     def password = findProperty("mySigningPassword")
    +     *     useInMemoryPgpKeys(secretKey, password)
    +     * }
    +     * 
    + * + * @since 5.4 + */ + @Incubating + public void useInMemoryPgpKeys(@Nullable String defaultSecretKey, @Nullable String defaultPassword) { + setSignatories(new InMemoryPgpSignatoryProvider(defaultSecretKey, defaultPassword)); + } + /** * The configuration that signature artifacts are added to. */ diff --git a/subprojects/signing/src/main/java/org/gradle/plugins/signing/signatory/internal/pgp/InMemoryPgpSignatoryProvider.java b/subprojects/signing/src/main/java/org/gradle/plugins/signing/signatory/internal/pgp/InMemoryPgpSignatoryProvider.java new file mode 100644 index 0000000000000..6cb27ccded55e --- /dev/null +++ b/subprojects/signing/src/main/java/org/gradle/plugins/signing/signatory/internal/pgp/InMemoryPgpSignatoryProvider.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.plugins.signing.signatory.internal.pgp; + +import groovy.lang.Closure; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.jcajce.JcaPGPSecretKeyRing; +import org.gradle.api.InvalidUserDataException; +import org.gradle.api.Project; +import org.gradle.plugins.signing.SigningExtension; +import org.gradle.plugins.signing.signatory.SignatoryProvider; +import org.gradle.plugins.signing.signatory.pgp.PgpSignatory; +import org.gradle.plugins.signing.signatory.pgp.PgpSignatoryFactory; +import org.gradle.util.ConfigureUtil; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.Map; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asType; + +/** + * A {@link SignatoryProvider} of {@link PgpSignatory} instances read from + * ascii-armored in-memory secret keys instead of a keyring. + */ +public class InMemoryPgpSignatoryProvider implements SignatoryProvider { + + private final PgpSignatoryFactory factory = new PgpSignatoryFactory(); + private final Map signatories = new LinkedHashMap<>(); + private final String defaultSecretKey; + private final String defaultPassword; + + public InMemoryPgpSignatoryProvider(String defaultSecretKey, String defaultPassword) { + this.defaultSecretKey = defaultSecretKey; + this.defaultPassword = defaultPassword; + } + + @Override + public PgpSignatory getDefaultSignatory(Project project) { + if (defaultSecretKey != null && defaultPassword != null) { + return createSignatory("default", defaultSecretKey, defaultPassword); + } + return null; + } + + @Override + public PgpSignatory getSignatory(String name) { + return signatories.get(name); + } + + @SuppressWarnings("unused") // invoked by Groovy + public PgpSignatory propertyMissing(String signatoryName) { + return getSignatory(signatoryName); + } + + @Override + @SuppressWarnings("rawtypes") + public void configure(SigningExtension settings, Closure closure) { + ConfigureUtil.configure(closure, new Object() { + @SuppressWarnings("unused") // invoked by Groovy + public void methodMissing(String name, Object args) { + createSignatoryFor(name, asType(args, Object[].class)); + } + }); + } + + private void createSignatoryFor(String name, Object[] args) { + if (args.length != 2) { + throw new IllegalArgumentException("Invalid args (" + name + ": " + Arrays.toString(args) + ")"); + } + String secretKey = args[0].toString(); + String password = args[1].toString(); + signatories.put(name, createSignatory(name, secretKey, password)); + } + + private PgpSignatory createSignatory(String name, String secretKey, String password) { + try (InputStream in = PGPUtil.getDecoderStream(new ByteArrayInputStream(secretKey.getBytes(UTF_8)))) { + PGPSecretKey key = new JcaPGPSecretKeyRing(in).getSecretKey(); + return factory.createSignatory(name, key, password); + } catch (Exception e) { + throw new InvalidUserDataException("Could not read PGP secret key", e); + } + } + +} diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy index 7303cc24f9c15..5f4cf7a3d6c7f 100644 --- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy +++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/AbstractSmokeTest.groovy @@ -41,16 +41,16 @@ abstract class AbstractSmokeTest extends Specification { static nebulaDependencyRecommender = "7.3.0" // https://plugins.gradle.org/plugin/nebula.plugin-plugin - static nebulaPluginPlugin = "9.2.4" + static nebulaPluginPlugin = "9.2.0" // https://plugins.gradle.org/plugin/nebula.lint static nebulaLint = "10.4.2" // https://plugins.gradle.org/plugin/nebula.dependency-lock - static nebulaDependencyLock = Versions.of("4.9.5", "5.0.6", "6.0.0", "7.0.1", "7.1.2") + static nebulaDependencyLock = Versions.of("4.9.5", "5.0.6", "6.0.0", "7.0.1", "7.1.2", "7.3.0") // https://plugins.gradle.org/plugin/nebula.resolution-rules - static nebulaResolutionRules = "7.0.7" + static nebulaResolutionRules = "7.0.8" // https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow static shadow = Versions.of("4.0.4") @@ -59,19 +59,19 @@ abstract class AbstractSmokeTest extends Specification { static asciidoctor = "1.5.9.2" // https://plugins.gradle.org/plugin/com.github.spotbugs - static spotbugs = "1.6.9" + static spotbugs = "1.6.10" // https://plugins.gradle.org/plugin/com.bmuschko.docker-java-application - static docker = "4.3.0" + static docker = "4.5.0" // https://plugins.gradle.org/plugin/com.bmuschko.tomcat static tomcat = "2.5" // https://plugins.gradle.org/plugin/io.spring.dependency-management - static springDependencyManagement = "1.0.6.RELEASE" + static springDependencyManagement = "1.0.7.RELEASE" // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-gradle-plugin - static springBoot = "2.1.2.RELEASE" + static springBoot = "2.1.3.RELEASE" // https://developer.android.com/studio/releases/build-tools static androidTools = "28.0.3" @@ -98,7 +98,7 @@ abstract class AbstractSmokeTest extends Specification { static grgit = "3.0.0" // https://plugins.gradle.org/plugin/com.github.ben-manes.versions - static gradleVersions = "0.20.0" + static gradleVersions = "0.21.0" // https://plugins.gradle.org/plugin/org.gradle.playframework static playframework = "0.4" diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/BuildScanPluginSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/BuildScanPluginSmokeTest.groovy index 117c4579b10a7..489274e433922 100644 --- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/BuildScanPluginSmokeTest.groovy +++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/BuildScanPluginSmokeTest.groovy @@ -33,6 +33,8 @@ class BuildScanPluginSmokeTest extends AbstractSmokeTest { ] private static final List SUPPORTED = [ + "2.2.1", + "2.2", "2.1", "2.0.2" ] diff --git a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyPluginsSmokeTest.groovy b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyPluginsSmokeTest.groovy index 0305dfc2fde79..5e7255d5b2cc2 100644 --- a/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyPluginsSmokeTest.groovy +++ b/subprojects/smoke-test/src/smokeTest/groovy/org/gradle/smoketests/ThirdPartyPluginsSmokeTest.groovy @@ -117,10 +117,10 @@ class ThirdPartyPluginsSmokeTest extends AbstractSmokeTest { """.stripIndent() when: - def result = runner('dockerSyncArchive').forwardOutput().build() + def result = runner('assemble').forwardOutput().build() then: - result.task(':dockerSyncArchive').outcome == SUCCESS + result.task(':assemble').outcome == SUCCESS } @Issue('https://plugins.gradle.org/plugin/io.spring.dependency-management') diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/change/ChangeType.java b/subprojects/snapshots/src/main/java/org/gradle/internal/change/ChangeTypeInternal.java similarity index 64% rename from subprojects/snapshots/src/main/java/org/gradle/internal/change/ChangeType.java rename to subprojects/snapshots/src/main/java/org/gradle/internal/change/ChangeTypeInternal.java index 76a876a8212aa..3e45417582111 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/change/ChangeType.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/change/ChangeTypeInternal.java @@ -16,18 +16,26 @@ package org.gradle.internal.change; -public enum ChangeType { - ADDED("has been added"), - MODIFIED("has changed"), - REMOVED("has been removed"); +import org.gradle.work.ChangeType; + +public enum ChangeTypeInternal { + ADDED("has been added", ChangeType.ADDED), + MODIFIED("has changed", ChangeType.MODIFIED), + REMOVED("has been removed", ChangeType.REMOVED); private final String description; + private final ChangeType publicType; - ChangeType(String description) { + ChangeTypeInternal(String description, ChangeType publicType) { this.description = description; + this.publicType = publicType; } public String describe() { return description; } + + public ChangeType getPublicType() { + return publicType; + } } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/change/FileChange.java b/subprojects/snapshots/src/main/java/org/gradle/internal/change/DefaultFileChange.java similarity index 50% rename from subprojects/snapshots/src/main/java/org/gradle/internal/change/FileChange.java rename to subprojects/snapshots/src/main/java/org/gradle/internal/change/DefaultFileChange.java index 9c0584b0c9ae6..c42861e831eaf 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/change/FileChange.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/change/DefaultFileChange.java @@ -19,51 +19,56 @@ import com.google.common.base.Objects; import org.gradle.api.tasks.incremental.InputFileDetails; import org.gradle.internal.file.FileType; +import org.gradle.work.ChangeType; +import org.gradle.work.FileChange; import java.io.File; -public class FileChange implements Change, InputFileDetails { +public class DefaultFileChange implements Change, FileChange, InputFileDetails { private final String path; - private final ChangeType change; + private final ChangeTypeInternal change; private final String title; private final FileType previousFileType; private final FileType currentFileType; + private final String normalizedPath; - public static FileChange added(String path, String title, FileType currentFileType) { - return new FileChange(path, ChangeType.ADDED, title, FileType.Missing, currentFileType); + public static DefaultFileChange added(String path, String title, FileType currentFileType, String normalizedPath) { + return new DefaultFileChange(path, ChangeTypeInternal.ADDED, title, FileType.Missing, currentFileType, normalizedPath); } - public static FileChange removed(String path, String title, FileType previousFileType) { - return new FileChange(path, ChangeType.REMOVED, title, previousFileType, FileType.Missing); + public static DefaultFileChange removed(String path, String title, FileType previousFileType, String normalizedPath) { + return new DefaultFileChange(path, ChangeTypeInternal.REMOVED, title, previousFileType, FileType.Missing, normalizedPath); } - public static FileChange modified(String path, String title, FileType previousFileType, FileType currentFileType) { - return new FileChange(path, ChangeType.MODIFIED, title, previousFileType, currentFileType); + public static DefaultFileChange modified(String path, String title, FileType previousFileType, FileType currentFileType, String normalizedPath) { + return new DefaultFileChange(path, ChangeTypeInternal.MODIFIED, title, previousFileType, currentFileType, normalizedPath); } - private FileChange(String path, ChangeType change, String title, FileType previousFileType, FileType currentFileType) { + private DefaultFileChange(String path, ChangeTypeInternal change, String title, FileType previousFileType, FileType currentFileType, String normalizedPath) { this.path = path; this.change = change; this.title = title; this.previousFileType = previousFileType; this.currentFileType = currentFileType; + this.normalizedPath = normalizedPath; } + @Override public String getMessage() { return title + " file " + path + " " + getDisplayedChangeType().describe() + "."; } - private ChangeType getDisplayedChangeType() { - if (change != ChangeType.MODIFIED) { + private ChangeTypeInternal getDisplayedChangeType() { + if (change != ChangeTypeInternal.MODIFIED) { return change; } if (previousFileType == FileType.Missing) { - return ChangeType.ADDED; + return ChangeTypeInternal.ADDED; } if (currentFileType == FileType.Missing) { - return ChangeType.REMOVED; + return ChangeTypeInternal.REMOVED; } - return ChangeType.MODIFIED; + return ChangeTypeInternal.MODIFIED; } @Override @@ -75,24 +80,44 @@ public String getPath() { return path; } + @Override public File getFile() { return new File(path); } - public ChangeType getType() { + @Override + public ChangeType getChangeType() { + return change.getPublicType(); + } + + @Override + public org.gradle.api.file.FileType getFileType() { + FileType typeToConvert = change == ChangeTypeInternal.REMOVED ? previousFileType : currentFileType; + return typeToConvert.toPublicType(); + } + + @Override + public String getNormalizedPath() { + return normalizedPath; + } + + public ChangeTypeInternal getType() { return change; } + @Override public boolean isAdded() { - return change == ChangeType.ADDED; + return change == ChangeTypeInternal.ADDED; } + @Override public boolean isModified() { - return change == ChangeType.MODIFIED; + return change == ChangeTypeInternal.MODIFIED; } + @Override public boolean isRemoved() { - return change == ChangeType.REMOVED; + return change == ChangeTypeInternal.REMOVED; } @Override @@ -103,16 +128,17 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) { return false; } - FileChange that = (FileChange) o; + DefaultFileChange that = (DefaultFileChange) o; return Objects.equal(path, that.path) && change == that.change && Objects.equal(title, that.title) && Objects.equal(previousFileType, that.previousFileType) - && Objects.equal(currentFileType, that.currentFileType); + && Objects.equal(currentFileType, that.currentFileType) + && Objects.equal(normalizedPath, that.normalizedPath); } @Override public int hashCode() { - return Objects.hashCode(path, change, title, previousFileType, currentFileType); + return Objects.hashCode(path, change, title, previousFileType, currentFileType, normalizedPath); } } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/FileCollectionFingerprint.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/FileCollectionFingerprint.java index f4cecce2075f3..03b66dc9fbfa0 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/FileCollectionFingerprint.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/FileCollectionFingerprint.java @@ -47,5 +47,10 @@ public Map getFingerprints() { public ImmutableMultimap getRootHashes() { return ImmutableMultimap.of(); } + + @Override + public String toString() { + return "EMPTY"; + } }; } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/AbsolutePathFingerprintCompareStrategy.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/AbsolutePathFingerprintCompareStrategy.java index 63ab8ba48cb48..177b5229e6eb4 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/AbsolutePathFingerprintCompareStrategy.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/AbsolutePathFingerprintCompareStrategy.java @@ -17,7 +17,7 @@ package org.gradle.internal.fingerprint.impl; import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.FileChange; +import org.gradle.internal.change.DefaultFileChange; import org.gradle.internal.fingerprint.FileSystemLocationFingerprint; import org.gradle.internal.fingerprint.FingerprintCompareStrategy; import org.gradle.internal.hash.HashCode; @@ -50,20 +50,23 @@ protected boolean doVisitChangesSince(ChangeVisitor visitor, Map entry : previous.entrySet()) { - Change change = FileChange.removed(entry.getKey(), propertyTitle, entry.getValue().getType()); + Change change = DefaultFileChange.removed(entry.getKey(), propertyTitle, entry.getValue().getType(), entry.getValue().getNormalizedPath()); if (!visitor.visitChange(change)) { return false; } @@ -89,7 +89,7 @@ static Boolean compareTrivialFingerprints(ChangeVisitor visitor, Map current, String propertyTitle, boolean includeAdded) { if (includeAdded) { for (Map.Entry entry : current.entrySet()) { - Change change = FileChange.added(entry.getKey(), propertyTitle, entry.getValue().getType()); + Change change = DefaultFileChange.added(entry.getKey(), propertyTitle, entry.getValue().getType(), entry.getValue().getNormalizedPath()); if (!visitor.visitChange(change)) { return false; } @@ -106,16 +106,16 @@ private static boolean compareTrivialFingerprintEntries(ChangeVisitor visitor, M HashCode currentContent = currentFingerprint.getNormalizedContentHash(); if (!currentContent.equals(previousContent)) { String path = currentEntry.getKey(); - Change change = FileChange.modified(path, propertyTitle, previousFingerprint.getType(), currentFingerprint.getType()); + Change change = DefaultFileChange.modified(path, propertyTitle, previousFingerprint.getType(), currentFingerprint.getType(), currentFingerprint.getNormalizedPath()); return visitor.visitChange(change); } return true; } else { String previousPath = previousEntry.getKey(); - Change remove = FileChange.removed(previousPath, propertyTitle, previousFingerprint.getType()); + Change remove = DefaultFileChange.removed(previousPath, propertyTitle, previousFingerprint.getType(), previousFingerprint.getNormalizedPath()); if (includeAdded) { String currentPath = currentEntry.getKey(); - Change add = FileChange.added(currentPath, propertyTitle, currentFingerprint.getType()); + Change add = DefaultFileChange.added(currentPath, propertyTitle, currentFingerprint.getType(), currentFingerprint.getNormalizedPath()); return visitor.visitChange(remove) && visitor.visitChange(add); } else { return visitor.visitChange(remove); diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprint.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprint.java index d7094c1a5dad5..60c02115b33d3 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprint.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprint.java @@ -18,7 +18,7 @@ import com.google.common.collect.ImmutableMultimap; import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.FileChange; +import org.gradle.internal.change.DefaultFileChange; import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint; import org.gradle.internal.fingerprint.FileCollectionFingerprint; import org.gradle.internal.fingerprint.FileSystemLocationFingerprint; @@ -42,7 +42,8 @@ public EmptyCurrentFileCollectionFingerprint(String identifier) { @Override public boolean visitChangesSince(FileCollectionFingerprint oldFingerprint, final String title, boolean includeAdded, ChangeVisitor visitor) { for (Map.Entry entry : oldFingerprint.getFingerprints().entrySet()) { - if (!visitor.visitChange(FileChange.removed(entry.getKey(), title, entry.getValue().getType()))) { + DefaultFileChange removed = DefaultFileChange.removed(entry.getKey(), title, entry.getValue().getType(), entry.getValue().getNormalizedPath()); + if (!visitor.visitChange(removed)) { return false; } } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/FilePathWithType.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/FilePathWithType.java index de48ce6bd1349..75749e4feb048 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/FilePathWithType.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/FilePathWithType.java @@ -16,13 +16,13 @@ package org.gradle.internal.fingerprint.impl; -import org.gradle.internal.change.FileChange; +import org.gradle.internal.change.DefaultFileChange; import org.gradle.internal.file.FileType; /** * The absolute path and the type of a file. * - * Used to construct {@link FileChange}s. + * Used to construct {@link DefaultFileChange}s. */ public class FilePathWithType { private final String absolutePath; diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/IgnoredPathCompareStrategy.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/IgnoredPathCompareStrategy.java index d1ec386d7a1b5..4d96fb255b4d6 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/IgnoredPathCompareStrategy.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/IgnoredPathCompareStrategy.java @@ -20,7 +20,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.MultimapBuilder; import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.FileChange; +import org.gradle.internal.change.DefaultFileChange; import org.gradle.internal.fingerprint.FileCollectionFingerprint; import org.gradle.internal.fingerprint.FileSystemLocationFingerprint; import org.gradle.internal.fingerprint.FingerprintCompareStrategy; @@ -74,7 +74,8 @@ protected boolean doVisitChangesSince(ChangeVisitor visitor, Map previousFilesForContent = unaccountedForPreviousFiles.get(normalizedContentHash); if (previousFilesForContent.isEmpty()) { if (includeAdded) { - if (!visitor.visitChange(FileChange.added(currentAbsolutePath, propertyTitle, currentFingerprint.getType()))) { + DefaultFileChange added = DefaultFileChange.added(currentAbsolutePath, propertyTitle, currentFingerprint.getType(), IgnoredPathFingerprintingStrategy.IGNORED_PATH); + if (!visitor.visitChange(added)) { return false; } } @@ -87,7 +88,8 @@ protected boolean doVisitChangesSince(ChangeVisitor visitor, Map unaccountedForPreviousEntry : unaccountedForPreviousEntries) { FilePathWithType removedFile = unaccountedForPreviousEntry.getValue(); - if (!visitor.visitChange(FileChange.removed(removedFile.getAbsolutePath(), propertyTitle, removedFile.getFileType()))) { + DefaultFileChange removed = DefaultFileChange.removed(removedFile.getAbsolutePath(), propertyTitle, removedFile.getFileType(), IgnoredPathFingerprintingStrategy.IGNORED_PATH); + if (!visitor.visitChange(removed)) { return false; } } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/IgnoredPathFingerprintingStrategy.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/IgnoredPathFingerprintingStrategy.java index 5ae26fcede061..1fb3d2bcaa912 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/IgnoredPathFingerprintingStrategy.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/IgnoredPathFingerprintingStrategy.java @@ -34,6 +34,7 @@ public class IgnoredPathFingerprintingStrategy extends AbstractFingerprintingStrategy { public static final IgnoredPathFingerprintingStrategy INSTANCE = new IgnoredPathFingerprintingStrategy(); + public static final String IGNORED_PATH = ""; private IgnoredPathFingerprintingStrategy() { super("IGNORED_PATH", IgnoredPathCompareStrategy.INSTANCE); @@ -41,7 +42,7 @@ private IgnoredPathFingerprintingStrategy() { @Override public String normalizePath(FileSystemLocationSnapshot snapshot) { - return ""; + return IGNORED_PATH; } @Override diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/NormalizedPathFingerprintCompareStrategy.java b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/NormalizedPathFingerprintCompareStrategy.java index 489310073ee6e..4d79d566e41c2 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/NormalizedPathFingerprintCompareStrategy.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/fingerprint/impl/NormalizedPathFingerprintCompareStrategy.java @@ -20,7 +20,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.MultimapBuilder; import org.gradle.internal.change.ChangeVisitor; -import org.gradle.internal.change.FileChange; +import org.gradle.internal.change.DefaultFileChange; import org.gradle.internal.fingerprint.FileSystemLocationFingerprint; import org.gradle.internal.fingerprint.FingerprintCompareStrategy; import org.gradle.internal.hash.Hasher; @@ -88,20 +88,24 @@ protected boolean doVisitChangesSince(ChangeVisitor visitor, Map entry : addedFilesByNormalizedPath.entries()) { + FilePathWithType addedFile = entry.getValue(); + DefaultFileChange added = DefaultFileChange.added(addedFile.getAbsolutePath(), propertyTitle, addedFile.getFileType(), entry.getKey()); + if (!visitor.visitChange(added)) { return false; } } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractArraySnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractArraySnapshot.java index 076c453d8b58e..bcee27705252b 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractArraySnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractArraySnapshot.java @@ -22,7 +22,7 @@ import java.util.List; -public class AbstractArraySnapshot implements Hashable { +class AbstractArraySnapshot implements Hashable { protected final ImmutableList elements; public AbstractArraySnapshot(ImmutableList elements) { diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractIsolatableScalarValue.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractIsolatableScalarValue.java index 81931e29cc59d..9b4a89efe8534 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractIsolatableScalarValue.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractIsolatableScalarValue.java @@ -26,7 +26,7 @@ * * @param */ -public abstract class AbstractIsolatableScalarValue extends AbstractScalarValueSnapshot implements Isolatable { +abstract class AbstractIsolatableScalarValue extends AbstractScalarValueSnapshot implements Isolatable { public AbstractIsolatableScalarValue(T value) { super(value); } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractListSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractListSnapshot.java index f997c4f73d075..bdffd902145dd 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractListSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractListSnapshot.java @@ -22,7 +22,7 @@ import java.util.List; -public class AbstractListSnapshot implements Hashable { +class AbstractListSnapshot implements Hashable { protected final ImmutableList elements; public AbstractListSnapshot(ImmutableList elements) { diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractManagedTypeSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractManagedValueSnapshot.java similarity index 86% rename from subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractManagedTypeSnapshot.java rename to subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractManagedValueSnapshot.java index 267fa5c4da923..5cd32881c6018 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractManagedTypeSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractManagedValueSnapshot.java @@ -19,10 +19,10 @@ import org.gradle.internal.hash.Hashable; import org.gradle.internal.hash.Hasher; -public class AbstractManagedTypeSnapshot implements Hashable { +class AbstractManagedValueSnapshot implements Hashable { protected final T state; - public AbstractManagedTypeSnapshot(T state) { + public AbstractManagedValueSnapshot(T state) { this.state = state; } @@ -38,7 +38,7 @@ public boolean equals(Object obj) { if (obj == null || obj.getClass() != getClass()) { return false; } - AbstractManagedTypeSnapshot other = (AbstractManagedTypeSnapshot) obj; + AbstractManagedValueSnapshot other = (AbstractManagedValueSnapshot) obj; return state.equals(other.state); } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractMapSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractMapSnapshot.java index 8dc0ee4b5957f..6998219f95030 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractMapSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractMapSnapshot.java @@ -21,7 +21,7 @@ import org.gradle.internal.hash.Hashable; import org.gradle.internal.hash.Hasher; -public class AbstractMapSnapshot implements Hashable { +class AbstractMapSnapshot implements Hashable { protected final ImmutableList> entries; public AbstractMapSnapshot(ImmutableList> entries) { diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractScalarValueSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractScalarValueSnapshot.java index 63a5ee6c40f3f..8eb1e4080899c 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractScalarValueSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractScalarValueSnapshot.java @@ -24,7 +24,7 @@ * * @param */ -public abstract class AbstractScalarValueSnapshot implements ValueSnapshot { +abstract class AbstractScalarValueSnapshot implements ValueSnapshot { private final T value; public AbstractScalarValueSnapshot(T value) { @@ -40,7 +40,11 @@ public ValueSnapshot snapshot(Object value, ValueSnapshotter snapshotter) { if (this.value.equals(value)) { return this; } - return snapshotter.snapshot(value); + ValueSnapshot snapshot = snapshotter.snapshot(value); + if (snapshot.equals(this)) { + return this; + } + return snapshot; } @Override diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractSetSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractSetSnapshot.java index e9e003d8704ef..eae45471f7395 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractSetSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/AbstractSetSnapshot.java @@ -20,7 +20,7 @@ import org.gradle.internal.hash.Hashable; import org.gradle.internal.hash.Hasher; -public class AbstractSetSnapshot implements Hashable { +class AbstractSetSnapshot implements Hashable { protected final ImmutableSet elements; public AbstractSetSnapshot(ImmutableSet elements) { diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/CoercingStringValueSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/CoercingStringValueSnapshot.java index 62c0996409b7f..71e4057d78b48 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/CoercingStringValueSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/CoercingStringValueSnapshot.java @@ -41,6 +41,9 @@ public S coerce(Class type) { if (Named.class.isAssignableFrom(type)) { return type.cast(instantiator.named(type.asSubclass(Named.class), getValue())); } + if (Integer.class.equals(type)) { + return type.cast(Integer.valueOf(getValue())); + } return null; } } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/DefaultValueSnapshotter.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/DefaultValueSnapshotter.java index 1afd07c2bffbc..191dd907640f3 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/DefaultValueSnapshotter.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/DefaultValueSnapshotter.java @@ -20,13 +20,10 @@ import com.google.common.collect.ImmutableSet; import org.gradle.api.UncheckedIOException; import org.gradle.api.attributes.Attribute; -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.internal.model.NamedObjectInstantiator; -import org.gradle.api.provider.Provider; import org.gradle.internal.Cast; import org.gradle.internal.Pair; import org.gradle.internal.classloader.ClassLoaderHierarchyHasher; -import org.gradle.internal.instantiation.Managed; +import org.gradle.internal.state.Managed; import org.gradle.internal.isolation.Isolatable; import org.gradle.internal.isolation.IsolatableFactory; import org.gradle.internal.isolation.IsolationException; @@ -47,9 +44,9 @@ public class DefaultValueSnapshotter implements ValueSnapshotter, IsolatableFact private final ValueVisitor valueSnapshotValueVisitor; private final ValueVisitor> isolatableValueVisitor; - public DefaultValueSnapshotter(ClassLoaderHierarchyHasher classLoaderHasher, NamedObjectInstantiator namedObjectInstantiator) { + public DefaultValueSnapshotter(ClassLoaderHierarchyHasher classLoaderHasher) { valueSnapshotValueVisitor = new ValueSnapshotVisitor(classLoaderHasher); - isolatableValueVisitor = new IsolatableVisitor(classLoaderHasher, namedObjectInstantiator); + isolatableValueVisitor = new IsolatableVisitor(classLoaderHasher); } @Override @@ -144,18 +141,12 @@ private T processValue(@Nullable Object value, ValueVisitor visitor) { if (value instanceof Attribute) { return visitor.attributeValue((Attribute) value); } - if (value instanceof Provider) { - Provider provider = (Provider) value; - Object providerValue = provider.get(); - T providerValueSnapshot = processValue(providerValue, visitor); - return visitor.provider(providerValue, providerValueSnapshot); - } if (value instanceof Managed) { Managed managed = (Managed) value; if (managed.immutable()) { return visitor.managedImmutableValue(managed); } else { - // May be mutable - unpack the state + // May (or may not) be mutable - unpack the state T state = processValue(managed.unpackState(), visitor); return visitor.managedValue(managed, state); } @@ -163,9 +154,6 @@ private T processValue(@Nullable Object value, ValueVisitor visitor) { if (value instanceof Isolatable) { return visitor.fromIsolatable((Isolatable) value); } - if (value instanceof ConfigurableFileCollection) { - return visitor.fromFileCollection((ConfigurableFileCollection) value); - } // Fall back to serialization return serialize(value, visitor); @@ -224,10 +212,6 @@ private interface ValueVisitor { T map(ImmutableList> elements); - T provider(Object value, T snapshot); - - T fromFileCollection(ConfigurableFileCollection files); - T serialized(Object value, byte[] serializedValue); } @@ -295,7 +279,7 @@ public ValueSnapshot managedImmutableValue(Managed managed) { @Override public ValueSnapshot managedValue(Managed value, ValueSnapshot state) { - return new ManagedTypeSnapshot(value.publicType().getName(), state); + return new ManagedValueSnapshot(value.publicType().getName(), state); } @Override @@ -303,16 +287,6 @@ public ValueSnapshot fromIsolatable(Isolatable value) { return value.asSnapshot(); } - @Override - public ValueSnapshot provider(Object value, ValueSnapshot snapshot) { - return new ProviderSnapshot(snapshot); - } - - @Override - public ValueSnapshot fromFileCollection(ConfigurableFileCollection files) { - throw new UnsupportedOperationException("Not implemented"); - } - @Override public ValueSnapshot serialized(Object value, byte[] serializedValue) { return new SerializedValueSnapshot(classLoaderHasher.getClassLoaderHash(value.getClass().getClassLoader()), serializedValue); @@ -351,11 +325,9 @@ public ValueSnapshot map(ImmutableList> eleme private static class IsolatableVisitor implements ValueVisitor> { private final ClassLoaderHierarchyHasher classLoaderHasher; - private final NamedObjectInstantiator namedObjectInstantiator; - IsolatableVisitor(ClassLoaderHierarchyHasher classLoaderHasher, NamedObjectInstantiator namedObjectInstantiator) { + IsolatableVisitor(ClassLoaderHierarchyHasher classLoaderHasher) { this.classLoaderHasher = classLoaderHasher; - this.namedObjectInstantiator = namedObjectInstantiator; } @Override @@ -415,7 +387,7 @@ public Isolatable managedImmutableValue(Managed managed) { @Override public Isolatable managedValue(Managed value, Isolatable state) { - return new IsolatedManagedTypeSnapshot(value.publicType(), value.managedFactory(), state); + return new IsolatedManagedValue(value.publicType(), value.managedFactory(), state); } @Override @@ -425,17 +397,7 @@ public Isolatable fromIsolatable(Isolatable value) { @Override public Isolatable serialized(Object value, byte[] serializedValue) { - return new IsolatableSerializedValueSnapshot(classLoaderHasher.getClassLoaderHash(value.getClass().getClassLoader()), serializedValue, value.getClass()); - } - - @Override - public Isolatable provider(Object value, Isolatable snapshot) { - throw new IsolationException(value); - } - - @Override - public Isolatable fromFileCollection(ConfigurableFileCollection files) { - return new IsolatedFileCollection(files); + return new IsolatedSerializedValueSnapshot(classLoaderHasher.getClassLoaderHash(value.getClass().getClassLoader()), serializedValue, value.getClass()); } @Override diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ImplementationSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ImplementationSnapshot.java index e23a6d6e825fe..3f5c05ca52c0d 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ImplementationSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ImplementationSnapshot.java @@ -77,7 +77,7 @@ public String getTypeName() { public abstract String getUnknownReason(); @Override - public ValueSnapshot snapshot(Object value, ValueSnapshotter snapshotter) { + public ValueSnapshot snapshot(@Nullable Object value, ValueSnapshotter snapshotter) { ValueSnapshot other = snapshotter.snapshot(value); if (this.isSameSnapshot(other)) { return this; @@ -85,7 +85,7 @@ public ValueSnapshot snapshot(Object value, ValueSnapshotter snapshotter) { return other; } - protected abstract boolean isSameSnapshot(Object o); + protected abstract boolean isSameSnapshot(@Nullable Object o); private static class DefaultImplementationSnapshot extends ImplementationSnapshot { private final HashCode classLoaderHash; @@ -103,7 +103,7 @@ public void appendToHasher(Hasher hasher) { } @Override - protected boolean isSameSnapshot(Object o) { + protected boolean isSameSnapshot(@Nullable Object o) { if (this == o) { return true; } @@ -177,7 +177,7 @@ public void appendToHasher(Hasher hasher) { } @Override - protected boolean isSameSnapshot(Object o) { + protected boolean isSameSnapshot(@Nullable Object o) { if (this == o) { return true; } @@ -234,7 +234,7 @@ public void appendToHasher(Hasher hasher) { } @Override - protected boolean isSameSnapshot(Object o) { + protected boolean isSameSnapshot(@Nullable Object o) { if (this == o) { return true; } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedFileCollection.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedFileCollection.java deleted file mode 100644 index 45fe4c9962aa2..0000000000000 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedFileCollection.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2019 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.snapshot.impl; - -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.internal.file.IdentityFileResolver; -import org.gradle.api.internal.file.collections.DefaultConfigurableFileCollection; -import org.gradle.internal.hash.Hasher; -import org.gradle.internal.isolation.Isolatable; -import org.gradle.internal.snapshot.ValueSnapshot; - -import javax.annotation.Nullable; -import java.io.File; -import java.util.Set; - -public class IsolatedFileCollection implements Isolatable { - private final Set files; - - public IsolatedFileCollection(ConfigurableFileCollection files) { - this.files = files.getFiles(); - } - - @Override - public ValueSnapshot asSnapshot() { - throw new UnsupportedOperationException("Not implemented"); - } - - @Override - public void appendToHasher(Hasher hasher) { - hasher.putString("files"); - for (File file : files) { - hasher.putString(file.getAbsolutePath()); - } - } - - @Nullable - @Override - public ConfigurableFileCollection isolate() { - return new DefaultConfigurableFileCollection(new IdentityFileResolver(), null, files); - } - - @Nullable - @Override - public S coerce(Class type) { - if (type.isAssignableFrom(ConfigurableFileCollection.class)) { - return type.cast(isolate()); - } - return null; - } -} diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedImmutableManagedValue.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedImmutableManagedValue.java index 548768310407f..92900cdc2a7b2 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedImmutableManagedValue.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedImmutableManagedValue.java @@ -17,8 +17,8 @@ package org.gradle.internal.snapshot.impl; import org.gradle.internal.hash.Hasher; -import org.gradle.internal.instantiation.Managed; import org.gradle.internal.snapshot.ValueSnapshot; +import org.gradle.internal.state.Managed; import javax.annotation.Nullable; @@ -43,9 +43,6 @@ public S coerce(Class type) { if (type.isInstance(getValue())) { return type.cast(getValue()); } - if (type.getName().equals(getValue().publicType().getName())) { - return type.cast(getValue().managedFactory().fromState(type, getValue().unpackState())); - } - return null; + return type.cast(getValue().managedFactory().fromState(type, getValue().unpackState())); } } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedList.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedList.java index 09f9ae5b6ecb1..3354d2375e17f 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedList.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedList.java @@ -24,7 +24,7 @@ import java.util.ArrayList; import java.util.List; -public class IsolatedList extends AbstractListSnapshot> implements Isolatable> { +class IsolatedList extends AbstractListSnapshot> implements Isolatable> { public static final IsolatedList EMPTY = new IsolatedList(ImmutableList.of()); public IsolatedList(ImmutableList> elements) { diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedManagedTypeSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedManagedValue.java similarity index 71% rename from subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedManagedTypeSnapshot.java rename to subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedManagedValue.java index 07ba848d42185..5428e0d09caf9 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedManagedTypeSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedManagedValue.java @@ -16,17 +16,17 @@ package org.gradle.internal.snapshot.impl; -import org.gradle.internal.instantiation.Managed; +import org.gradle.internal.state.Managed; import org.gradle.internal.isolation.Isolatable; import org.gradle.internal.snapshot.ValueSnapshot; import javax.annotation.Nullable; -public class IsolatedManagedTypeSnapshot extends AbstractManagedTypeSnapshot> implements Isolatable { +class IsolatedManagedValue extends AbstractManagedValueSnapshot> implements Isolatable { private final Managed.Factory factory; private final Class targetType; - public IsolatedManagedTypeSnapshot(Class targetType, Managed.Factory factory, Isolatable state) { + public IsolatedManagedValue(Class targetType, Managed.Factory factory, Isolatable state) { super(state); this.targetType = targetType; this.factory = factory; @@ -34,7 +34,7 @@ public IsolatedManagedTypeSnapshot(Class targetType, Managed.Factory factory, @Override public ValueSnapshot asSnapshot() { - return new ManagedTypeSnapshot(targetType.getName(), state.asSnapshot()); + return new ManagedValueSnapshot(targetType.getName(), state.asSnapshot()); } @Override @@ -48,9 +48,6 @@ public S coerce(Class type) { if (type.isAssignableFrom(targetType)) { return type.cast(isolate()); } - if (targetType.getName().equals(type.getName())) { - return type.cast(factory.fromState(type, state.isolate())); - } - return null; + return type.cast(factory.fromState(type, state.isolate())); } } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedMap.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedMap.java index cbb1b684608fe..7f742d145d0ec 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedMap.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedMap.java @@ -25,7 +25,7 @@ import java.util.LinkedHashMap; import java.util.Map; -public class IsolatedMap extends AbstractMapSnapshot> implements Isolatable> { +class IsolatedMap extends AbstractMapSnapshot> implements Isolatable> { public IsolatedMap(ImmutableList, Isolatable>> entries) { super(entries); } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatableSerializedValueSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedSerializedValueSnapshot.java similarity index 87% rename from subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatableSerializedValueSnapshot.java rename to subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedSerializedValueSnapshot.java index 0e672504abcc0..ea50095317ce4 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatableSerializedValueSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedSerializedValueSnapshot.java @@ -25,10 +25,10 @@ /** * Isolates a Serialized value and is a snapshot for that value. */ -public class IsolatableSerializedValueSnapshot extends SerializedValueSnapshot implements Isolatable { +public class IsolatedSerializedValueSnapshot extends SerializedValueSnapshot implements Isolatable { private final Class originalClass; - public IsolatableSerializedValueSnapshot(HashCode implementationHash, byte[] serializedValue, Class originalClass) { + public IsolatedSerializedValueSnapshot(HashCode implementationHash, byte[] serializedValue, Class originalClass) { super(implementationHash, serializedValue); this.originalClass = originalClass; } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedSet.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedSet.java index c38c62a6581d5..0ed458136329f 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedSet.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/IsolatedSet.java @@ -24,7 +24,7 @@ import java.util.LinkedHashSet; import java.util.Set; -public class IsolatedSet extends AbstractSetSnapshot> implements Isolatable> { +class IsolatedSet extends AbstractSetSnapshot> implements Isolatable> { public IsolatedSet(ImmutableSet> elements) { super(elements); } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ManagedTypeSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ManagedValueSnapshot.java similarity index 75% rename from subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ManagedTypeSnapshot.java rename to subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ManagedValueSnapshot.java index aeefa70cb7188..584cec13eac26 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ManagedTypeSnapshot.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ManagedValueSnapshot.java @@ -19,10 +19,10 @@ import org.gradle.internal.snapshot.ValueSnapshot; import org.gradle.internal.snapshot.ValueSnapshotter; -public class ManagedTypeSnapshot extends AbstractManagedTypeSnapshot implements ValueSnapshot { +public class ManagedValueSnapshot extends AbstractManagedValueSnapshot implements ValueSnapshot { private final String className; - public ManagedTypeSnapshot(String className, ValueSnapshot state) { + public ManagedValueSnapshot(String className, ValueSnapshot state) { super(state); this.className = className; } @@ -36,7 +36,7 @@ public boolean equals(Object obj) { if (!super.equals(obj)) { return false; } - ManagedTypeSnapshot other = (ManagedTypeSnapshot) obj; + ManagedValueSnapshot other = (ManagedValueSnapshot) obj; return className.equals(other.className); } @@ -48,11 +48,8 @@ public int hashCode() { @Override public ValueSnapshot snapshot(Object value, ValueSnapshotter snapshotter) { ValueSnapshot snapshot = snapshotter.snapshot(value); - if (snapshot instanceof ManagedTypeSnapshot) { - ManagedTypeSnapshot other = (ManagedTypeSnapshot) snapshot; - if (state.equals(other.state)) { - return this; - } + if (snapshot.equals(this)) { + return this; } return snapshot; } diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ProviderSnapshot.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ProviderSnapshot.java deleted file mode 100644 index db5c91c73744f..0000000000000 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/ProviderSnapshot.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2017 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.gradle.internal.snapshot.impl; - -import org.gradle.internal.hash.Hasher; -import org.gradle.internal.snapshot.ValueSnapshot; -import org.gradle.internal.snapshot.ValueSnapshotter; - -public class ProviderSnapshot implements ValueSnapshot { - private final ValueSnapshot valueSnapshot; - - ProviderSnapshot(ValueSnapshot valueSnapshot) { - this.valueSnapshot = valueSnapshot; - } - - public ValueSnapshot getValue() { - return valueSnapshot; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != getClass()) { - return false; - } - ProviderSnapshot other = (ProviderSnapshot) obj; - return other.valueSnapshot.equals(valueSnapshot); - } - - @Override - public int hashCode() { - return valueSnapshot.hashCode(); - } - - @Override - public ValueSnapshot snapshot(Object value, ValueSnapshotter snapshotter) { - ValueSnapshot snapshot = snapshotter.snapshot(value); - if (equals(snapshot)) { - return this; - } - return snapshot; - } - - @Override - public void appendToHasher(Hasher hasher) { - valueSnapshot.appendToHasher(hasher); - } -} diff --git a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/SnapshotSerializer.java b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/SnapshotSerializer.java index 0db590cf9862b..1776bb1ba4e62 100644 --- a/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/SnapshotSerializer.java +++ b/subprojects/snapshots/src/main/java/org/gradle/internal/snapshot/impl/SnapshotSerializer.java @@ -41,11 +41,10 @@ public class SnapshotSerializer extends AbstractSerializer { private static final int LIST_SNAPSHOT = 12; private static final int SET_SNAPSHOT = 13; private static final int MAP_SNAPSHOT = 14; - private static final int PROVIDER_SNAPSHOT = 15; - private static final int MANAGED_SNAPSHOT = 16; - private static final int IMMUTABLE_MANAGED_SNAPSHOT = 17; - private static final int IMPLEMENTATION_SNAPSHOT = 18; - private static final int DEFAULT_SNAPSHOT = 19; + private static final int MANAGED_SNAPSHOT = 15; + private static final int IMMUTABLE_MANAGED_SNAPSHOT = 16; + private static final int IMPLEMENTATION_SNAPSHOT = 17; + private static final int DEFAULT_SNAPSHOT = 18; private final HashCodeSerializer serializer = new HashCodeSerializer(); private final Serializer implementationSnapshotSerializer = new ImplementationSnapshot.SerializerImpl(); @@ -100,12 +99,10 @@ public ValueSnapshot read(Decoder decoder) throws Exception { mapBuilder.add(Pair.of(read(decoder), read(decoder))); } return new MapValueSnapshot(mapBuilder.build()); - case PROVIDER_SNAPSHOT: - return new ProviderSnapshot(read(decoder)); case MANAGED_SNAPSHOT: className = decoder.readString(); ValueSnapshot state = read(decoder); - return new ManagedTypeSnapshot(className, state); + return new ManagedValueSnapshot(className, state); case IMMUTABLE_MANAGED_SNAPSHOT: className = decoder.readString(); String value = decoder.readString(); @@ -211,18 +208,14 @@ public void write(Encoder encoder, ValueSnapshot snapshot) throws Exception { write(encoder, valueSnapshot); } } - } else if (snapshot instanceof ProviderSnapshot) { - encoder.writeSmallInt(PROVIDER_SNAPSHOT); - ProviderSnapshot providerSnapshot = (ProviderSnapshot) snapshot; - write(encoder, providerSnapshot.getValue()); } else if (snapshot instanceof ImmutableManagedValueSnapshot) { encoder.writeSmallInt(IMMUTABLE_MANAGED_SNAPSHOT); ImmutableManagedValueSnapshot valueSnapshot = (ImmutableManagedValueSnapshot) snapshot; encoder.writeString(valueSnapshot.getClassName()); encoder.writeString(valueSnapshot.getValue()); - } else if (snapshot instanceof ManagedTypeSnapshot) { + } else if (snapshot instanceof ManagedValueSnapshot) { encoder.writeSmallInt(MANAGED_SNAPSHOT); - ManagedTypeSnapshot managedTypeSnapshot = (ManagedTypeSnapshot) snapshot; + ManagedValueSnapshot managedTypeSnapshot = (ManagedValueSnapshot) snapshot; encoder.writeString(managedTypeSnapshot.getClassName()); write(encoder, managedTypeSnapshot.getState()); } else { diff --git a/subprojects/snapshots/src/test/groovy/org/gradle/internal/change/FileChangeTest.groovy b/subprojects/snapshots/src/test/groovy/org/gradle/internal/change/DefaultFileChangeTest.groovy similarity index 72% rename from subprojects/snapshots/src/test/groovy/org/gradle/internal/change/FileChangeTest.groovy rename to subprojects/snapshots/src/test/groovy/org/gradle/internal/change/DefaultFileChangeTest.groovy index eb778d2deec88..871be1357747b 100644 --- a/subprojects/snapshots/src/test/groovy/org/gradle/internal/change/FileChangeTest.groovy +++ b/subprojects/snapshots/src/test/groovy/org/gradle/internal/change/DefaultFileChangeTest.groovy @@ -21,11 +21,11 @@ import spock.lang.Specification import spock.lang.Unroll @Unroll -class FileChangeTest extends Specification { +class DefaultFileChangeTest extends Specification { def "change message for ChangeType MODIFIED from #previous to #current is '#message'"() { expect: - FileChange.modified("somePath", "test", previous, current).message == "test file somePath ${message}." + DefaultFileChange.modified("somePath", "test", previous, current, "").message == "test file somePath ${message}." where: previous | current | message @@ -41,10 +41,10 @@ class FileChangeTest extends Specification { fileChange.message == "test file somePath ${message}." where: - fileChange | message - FileChange.removed("somePath", "test", FileType.RegularFile) | "has been removed" - FileChange.removed("somePath", "test", FileType.Directory) | "has been removed" - FileChange.added("somePath", "test", FileType.RegularFile) | "has been added" - FileChange.added("somePath", "test", FileType.Directory) | "has been added" + fileChange | message + DefaultFileChange.removed("somePath", "test", FileType.RegularFile, "") | "has been removed" + DefaultFileChange.removed("somePath", "test", FileType.Directory, "") | "has been removed" + DefaultFileChange.added("somePath", "test", FileType.RegularFile, "") | "has been added" + DefaultFileChange.added("somePath", "test", FileType.Directory, "") | "has been added" } } diff --git a/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/AbsolutePathFileCollectionFingerprinterTest.groovy b/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/AbsolutePathFileCollectionFingerprinterTest.groovy index b73de194f9dac..05268575cc120 100644 --- a/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/AbsolutePathFileCollectionFingerprinterTest.groovy +++ b/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/AbsolutePathFileCollectionFingerprinterTest.groovy @@ -19,9 +19,9 @@ import org.gradle.api.file.FileCollection import org.gradle.api.internal.cache.StringInterner import org.gradle.api.internal.file.TestFiles import org.gradle.api.internal.file.collections.ImmutableFileCollection -import org.gradle.internal.change.ChangeType +import org.gradle.internal.change.ChangeTypeInternal import org.gradle.internal.change.CollectingChangeVisitor -import org.gradle.internal.change.FileChange +import org.gradle.internal.change.DefaultFileChange import org.gradle.internal.fingerprint.FileCollectionFingerprint import org.gradle.internal.hash.TestFileHasher import org.gradle.internal.snapshot.WellKnownFileLocations @@ -282,15 +282,15 @@ class AbsolutePathFileCollectionFingerprinterTest extends Specification { } private static void changes(FileCollectionFingerprint newFingerprint, FileCollectionFingerprint oldFingerprint, ChangeListener listener) { - newFingerprint.visitChangesSince(oldFingerprint, "TYPE", true) { FileChange change -> + newFingerprint.visitChangesSince(oldFingerprint, "TYPE", true) { DefaultFileChange change -> switch (change.type) { - case ChangeType.ADDED: + case ChangeTypeInternal.ADDED: listener.added(change.path) break - case ChangeType.MODIFIED: + case ChangeTypeInternal.MODIFIED: listener.changed(change.path) break - case ChangeType.REMOVED: + case ChangeTypeInternal.REMOVED: listener.removed(change.path) break } diff --git a/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprintTest.groovy b/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprintTest.groovy index 6b374650f3ff8..552686c4df48a 100644 --- a/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprintTest.groovy +++ b/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/EmptyCurrentFileCollectionFingerprintTest.groovy @@ -19,7 +19,7 @@ package org.gradle.internal.fingerprint.impl import com.google.common.collect.ImmutableMultimap import org.gradle.internal.change.Change import org.gradle.internal.change.CollectingChangeVisitor -import org.gradle.internal.change.FileChange +import org.gradle.internal.change.DefaultFileChange import org.gradle.internal.file.FileType import org.gradle.internal.fingerprint.CurrentFileCollectionFingerprint import org.gradle.internal.fingerprint.FileCollectionFingerprint @@ -42,8 +42,8 @@ class EmptyCurrentFileCollectionFingerprintTest extends Specification { } expect: getChanges(fingerprint, empty, includeAdded).toList() == [ - FileChange.removed("file1.txt", "test", FileType.RegularFile), - FileChange.removed("file2.txt", "test", FileType.RegularFile) + DefaultFileChange.removed("file1.txt", "test", FileType.RegularFile, "file1.txt"), + DefaultFileChange.removed("file2.txt", "test", FileType.RegularFile, "file2.txt") ] where: diff --git a/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/FingerprintCompareStrategyTest.groovy b/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/FingerprintCompareStrategyTest.groovy index 5c3ba518b8fdd..514114cd6f35b 100644 --- a/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/FingerprintCompareStrategyTest.groovy +++ b/subprojects/snapshots/src/test/groovy/org/gradle/internal/fingerprint/impl/FingerprintCompareStrategyTest.groovy @@ -16,8 +16,9 @@ package org.gradle.internal.fingerprint.impl +import com.google.common.collect.Iterables import org.gradle.internal.change.CollectingChangeVisitor -import org.gradle.internal.change.FileChange +import org.gradle.internal.change.DefaultFileChange import org.gradle.internal.file.FileType import org.gradle.internal.fingerprint.FileSystemLocationFingerprint import org.gradle.internal.fingerprint.FingerprintCompareStrategy @@ -61,11 +62,11 @@ class FingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - NORMALIZED | true | [added("one-new")] + NORMALIZED | true | [added("one-new": "one")] NORMALIZED | false | [] - IGNORED_PATH | true | [added("one-new")] + IGNORED_PATH | true | [added("one-new": "one")] IGNORED_PATH | false | [] - ABSOLUTE | true | [added("one-new")] + ABSOLUTE | true | [added("one-new": "one")] ABSOLUTE | false | [] } @@ -79,14 +80,14 @@ class FingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - NORMALIZED | true | [added("two-new")] + NORMALIZED | true | [added("two-new": "two")] NORMALIZED | false | [] } @Unroll def "non-trivial addition with absolute paths (#strategy, include added: #includeAdded)"() { expect: - changesUsingAbsolutePaths(strategy, includeAdded, + changes(strategy, includeAdded, ["one": fingerprint("one"), "two": fingerprint("two")], ["one": fingerprint("one")] ) == results @@ -103,7 +104,7 @@ class FingerprintCompareStrategyTest extends Specification { changes(strategy, includeAdded, [:], ["one-old": fingerprint("one")] - ) as List == [removed("one-old")] + ) as List == [removed("one-old": "one")] where: strategy | includeAdded @@ -119,7 +120,7 @@ class FingerprintCompareStrategyTest extends Specification { changes(strategy, includeAdded, ["one-new": fingerprint("one")], ["one-old": fingerprint("one"), "two-old": fingerprint("two")] - ) == [removed("two-old")] + ) == [removed("two-old": "two")] where: strategy | includeAdded @@ -130,7 +131,7 @@ class FingerprintCompareStrategyTest extends Specification { @Unroll def "non-trivial removal with absolute paths (#strategy, include added: #includeAdded)"() { expect: - changesUsingAbsolutePaths(strategy, includeAdded, + changes(strategy, includeAdded, ["one": fingerprint("one")], ["one": fingerprint("one"), "two": fingerprint("two")] ) == [removed("two")] @@ -147,7 +148,7 @@ class FingerprintCompareStrategyTest extends Specification { changes(strategy, includeAdded, ["one-new": fingerprint("one"), "two-new": fingerprint("two", 0x9876cafe)], ["one-old": fingerprint("one"), "two-old": fingerprint("two", 0xface1234)] - ) == [modified("two-new", FileType.RegularFile, FileType.RegularFile)] + ) == [modified("two-new": "two", FileType.RegularFile, FileType.RegularFile)] where: strategy | includeAdded @@ -161,7 +162,7 @@ class FingerprintCompareStrategyTest extends Specification { changes(NORMALIZED, includeAdded, ["two-new": fingerprint("", 0x9876cafe), "one-new": fingerprint("")], ["one-old": fingerprint(""), "two-old": fingerprint("", 0xface1234)] - ) == [modified("two-new", FileType.RegularFile, FileType.RegularFile)] + ) == [modified("two-new": "", FileType.RegularFile, FileType.RegularFile)] where: includeAdded << [true, false] @@ -170,7 +171,7 @@ class FingerprintCompareStrategyTest extends Specification { @Unroll def "non-trivial modification with absolute paths (#strategy, include added: #includeAdded)"() { expect: - changesUsingAbsolutePaths(strategy, includeAdded, + changes(strategy, includeAdded, ["one": fingerprint("one"), "two": fingerprint("two", 0x9876cafe)], ["one": fingerprint("one"), "two": fingerprint("two", 0xface1234)] ) == [modified("two", FileType.RegularFile, FileType.RegularFile)] @@ -191,8 +192,8 @@ class FingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - NORMALIZED | true | [removed("one-old"), added("two-new")] - NORMALIZED | false | [removed("one-old")] + NORMALIZED | true | [removed("one-old": "one"), added("two-new": "two")] + NORMALIZED | false | [removed("one-old": "one")] } @Unroll @@ -205,14 +206,14 @@ class FingerprintCompareStrategyTest extends Specification { where: strategy | includeAdded | results - NORMALIZED | true | [removed("three-old"), added("two-new")] - NORMALIZED | false | [removed("three-old")] + NORMALIZED | true | [removed("three-old": "three"), added("two-new": "two")] + NORMALIZED | false | [removed("three-old": "three")] } @Unroll def "non-trivial replacement with absolute paths (#strategy, include added: #includeAdded)"() { expect: - changesUsingAbsolutePaths(strategy, includeAdded, + changes(strategy, includeAdded, ["one": fingerprint("one"), "two": fingerprint("two"), "four": fingerprint("four")], ["one": fingerprint("one"), "three": fingerprint("three"), "four": fingerprint("four")] ) == results @@ -240,7 +241,7 @@ class FingerprintCompareStrategyTest extends Specification { @Unroll def "reordering with absolute paths (#strategy, include added: #includeAdded)"() { expect: - changesUsingAbsolutePaths(strategy, includeAdded, + changes(strategy, includeAdded, ["one": fingerprint("one"), "two": fingerprint("two"), "three": fingerprint("three")], ["one": fingerprint("one"), "three": fingerprint("three"), "two": fingerprint("two")] ) == results @@ -283,25 +284,34 @@ class FingerprintCompareStrategyTest extends Specification { visitor.getChanges().toList() } - def changesUsingAbsolutePaths(FingerprintCompareStrategy strategy, boolean includeAdded, Map current, Map previous) { - def visitor = new CollectingChangeVisitor() - strategy.visitChangesSince(visitor, current, previous, "test", includeAdded) - visitor.getChanges().toList() - } - def fingerprint(String normalizedPath, def hashCode = 0x1234abcd) { return new DefaultFileSystemLocationFingerprint(normalizedPath, FileType.RegularFile, HashCode.fromInt((int) hashCode)) } def added(String path) { - FileChange.added(path, "test", FileType.RegularFile) + added((path): path) + } + + def added(Map entry) { + def singleEntry = Iterables.getOnlyElement(entry.entrySet()) + DefaultFileChange.added(singleEntry.key, "test", FileType.RegularFile, singleEntry.value) } def removed(String path) { - FileChange.removed(path, "test", FileType.RegularFile) + removed((path): path) + } + + def removed(Map entry) { + def singleEntry = Iterables.getOnlyElement(entry.entrySet()) + DefaultFileChange.removed(singleEntry.key, "test", FileType.RegularFile, singleEntry.value) + } + + def modified(String path, FileType previous = FileType.RegularFile, FileType current = FileType.RegularFile) { + modified((path): path, previous, current) } - def modified(String path, FileType previous, FileType current) { - FileChange.modified(path, "test", previous, current) + def modified(Map paths, FileType previous = FileType.RegularFile, FileType current = FileType.RegularFile) { + def singleEntry = Iterables.getOnlyElement(paths.entrySet()) + DefaultFileChange.modified(singleEntry.key, "test", previous, current, singleEntry.value) } } diff --git a/subprojects/snapshots/src/test/groovy/org/gradle/internal/snapshot/impl/DefaultValueSnapshotterTest.groovy b/subprojects/snapshots/src/test/groovy/org/gradle/internal/snapshot/impl/DefaultValueSnapshotterTest.groovy index a7722f04238dc..a0b7647c3da22 100644 --- a/subprojects/snapshots/src/test/groovy/org/gradle/internal/snapshot/impl/DefaultValueSnapshotterTest.groovy +++ b/subprojects/snapshots/src/test/groovy/org/gradle/internal/snapshot/impl/DefaultValueSnapshotterTest.groovy @@ -19,7 +19,11 @@ package org.gradle.internal.snapshot.impl import org.gradle.api.Named import org.gradle.api.internal.file.TestFiles import org.gradle.api.internal.model.NamedObjectInstantiator -import org.gradle.api.provider.Provider +import org.gradle.api.internal.provider.DefaultListProperty +import org.gradle.api.internal.provider.DefaultMapProperty +import org.gradle.api.internal.provider.DefaultPropertyState +import org.gradle.api.internal.provider.DefaultSetProperty +import org.gradle.api.internal.provider.Providers import org.gradle.internal.classloader.ClassLoaderHierarchyHasher import org.gradle.internal.classloader.ClasspathUtil import org.gradle.internal.classloader.FilteringClassLoader @@ -33,7 +37,7 @@ class DefaultValueSnapshotterTest extends Specification { def classLoaderHasher = Stub(ClassLoaderHierarchyHasher) { getClassLoaderHash(_) >> HashCode.fromInt(123) } - def snapshotter = new DefaultValueSnapshotter(classLoaderHasher, NamedObjectInstantiator.INSTANCE) + def snapshotter = new DefaultValueSnapshotter(classLoaderHasher) def "creates snapshot for string"() { expect: @@ -215,6 +219,7 @@ class DefaultValueSnapshotterTest extends Specification { def "creates isolated null value"() { expect: snapshotter.isolate(null).isolate() == null + snapshotter.isolate(null).is(snapshotter.isolate(null)) } def "can coerce null value"() { @@ -251,15 +256,19 @@ class DefaultValueSnapshotterTest extends Specification { def "creates isolated array"() { expect: - def isolated1 = snapshotter.isolate([] as String[]) + def original1 = [] as String[] + def isolated1 = snapshotter.isolate(original1) isolated1 instanceof IsolatedArray - isolated1.isolate() == [] as String[] + def copy1 = isolated1.isolate() + copy1 == [] as String[] + !copy1.is(original1) - def original = ["123"] as String[] - def isolated2 = snapshotter.isolate(original) + def original2 = ["123"] as String[] + def isolated2 = snapshotter.isolate(original2) isolated2 instanceof IsolatedArray - isolated2.isolate() == ["123"] as String[] - !isolated2.isolate().is(original) + def copy2 = isolated2.isolate() + copy2 == ["123"] as String[] + !copy2.is(original2) } def "creates snapshot for isolated array"() { @@ -287,15 +296,19 @@ class DefaultValueSnapshotterTest extends Specification { def "creates isolated list"() { expect: - def isolated1 = snapshotter.isolate([]) + def original1 = [] + def isolated1 = snapshotter.isolate(original1) isolated1 instanceof IsolatedList - isolated1.isolate() == [] + def copy1 = isolated1.isolate() + copy1 == [] + !copy1.is(original1) - def original = ["123"] - def isolated2 = snapshotter.isolate(original) + def original2 = ["123"] + def isolated2 = snapshotter.isolate(original2) isolated2 instanceof IsolatedList - isolated2.isolate() == ["123"] - !isolated2.isolate().is(original) + def copy2 = isolated2.isolate() + copy2 == ["123"] + !copy2.is(original2) } def "creates snapshot for isolated list"() { @@ -335,15 +348,19 @@ class DefaultValueSnapshotterTest extends Specification { def "creates isolated set"() { expect: - def isolated1 = snapshotter.isolate([] as Set) + def original1 = [] as Set + def isolated1 = snapshotter.isolate(original1) isolated1 instanceof IsolatedSet - isolated1.isolate() == [] as Set + def copy1 = isolated1.isolate() + copy1 == [] as Set + !copy1.is(original1) - def original = ["123"] as Set - def isolated2 = snapshotter.isolate(original) + def original2 = ["123"] as Set + def isolated2 = snapshotter.isolate(original2) isolated2 instanceof IsolatedSet - isolated2.isolate() == ["123"] as Set - !isolated2.isolate().is(original) + def copy2 = isolated2.isolate() + copy2 == ["123"] as Set + !copy2.is(original2) } def "creates snapshot for isolated set"() { @@ -375,15 +392,19 @@ class DefaultValueSnapshotterTest extends Specification { def "creates isolated map"() { expect: - def isolated1 = snapshotter.isolate([:]) + def original1 = [:] + def isolated1 = snapshotter.isolate(original1) isolated1 instanceof IsolatedMap - isolated1.isolate() == [:] + def copy1 = isolated1.isolate() + copy1 == [:] + !copy1.is(original1) - def original = [a: "123"] - def isolated2 = snapshotter.isolate(original) + def original2 = [a: "123"] + def isolated2 = snapshotter.isolate(original2) isolated2 instanceof IsolatedMap - isolated2.isolate() == [a: "123"] - !isolated2.isolate().is(isolated2) + def copy2 = isolated2.isolate() + copy2 == [a: "123"] + !copy2.is(isolated2) } def "creates snapshot for isolated map"() { @@ -479,19 +500,83 @@ class DefaultValueSnapshotterTest extends Specification { } def "creates snapshot for provider type"() { - def value = Stub(Provider) - value.get() >> "123" - def value2 = Stub(Provider) - value2.get() >> "123" - def value3 = Stub(Provider) - value3.get() >> "12" + def value = Providers.of("123") + def value2 = Providers.of("123") + def value3 = Providers.of("12") expect: def snapshot = snapshotter.snapshot(value) - snapshot instanceof ProviderSnapshot snapshot == snapshotter.snapshot(value) snapshot == snapshotter.snapshot(value2) snapshot != snapshotter.snapshot(value3) + snapshot != snapshotter.snapshot("123") + } + + def "creates isolated provider"() { + def originalValue = "123" + def original = Providers.of(originalValue) + + expect: + def isolated = snapshotter.isolate(original) + isolated instanceof IsolatedManagedValue + def copy = isolated.isolate() + !copy.is(original) + copy.get().is(originalValue) + } + + def "creates isolated property"() { + def originalValue = "123" + def original = new DefaultPropertyState(String) + original.set(originalValue) + + expect: + def isolated = snapshotter.isolate(original) + isolated instanceof IsolatedManagedValue + def copy = isolated.isolate() + !copy.is(original) + copy.get().is(originalValue) + } + + def "creates isolated list property"() { + def originalValue = ["123"] + def original = new DefaultListProperty(String) + original.set(originalValue) + + expect: + def isolated = snapshotter.isolate(original) + isolated instanceof IsolatedManagedValue + def copy = isolated.isolate() + !copy.is(original) + copy.get() == ["123"] + !copy.get().is(originalValue) + } + + def "creates isolated set property"() { + def originalValue = ["123"] + def original = new DefaultSetProperty(String) + original.set(originalValue) + + expect: + def isolated = snapshotter.isolate(original) + isolated instanceof IsolatedManagedValue + def copy = isolated.isolate() + !copy.is(original) + copy.get() == ["123"] as Set + !copy.get().is(originalValue) + } + + def "creates isolated map property"() { + def originalMap = [a: 1, b: 2] + def original = new DefaultMapProperty(String, Number) + original.set(originalMap) + + expect: + def isolated = snapshotter.isolate(original) + isolated instanceof IsolatedManagedValue + def copy = isolated.isolate() + !copy.is(original) + copy.get() == [a: 1, b: 2] + !copy.get().is(originalMap) } def "creates snapshot for named managed type"() { @@ -527,7 +612,7 @@ class DefaultValueSnapshotterTest extends Specification { def spec = new FilteringClassLoader.Spec() spec.allowClass(Named) spec.allowPackage("org.gradle.api.internal.model") // mixed into the implementation - spec.allowPackage("org.gradle.internal.instantiation") // mixed into the implementation + spec.allowPackage("org.gradle.internal.state") // mixed into the implementation def filter = new FilteringClassLoader(getClass().classLoader, spec) def loader = new GroovyClassLoader(filter) loader.addURL(ClasspathUtil.getClasspathForClass(GroovyObject).toURI().toURL()) @@ -560,6 +645,7 @@ class DefaultValueSnapshotterTest extends Specification { interface BeanInterface { String getProp1() + void setProp1(String value) } @@ -576,7 +662,7 @@ class DefaultValueSnapshotterTest extends Specification { expect: def snapshot = snapshotter.snapshot(value) - snapshot instanceof ManagedTypeSnapshot + snapshot instanceof ManagedValueSnapshot snapshot == snapshotter.snapshot(value) snapshot == snapshotter.snapshot(value1) snapshot != snapshotter.snapshot(value2) @@ -585,14 +671,14 @@ class DefaultValueSnapshotterTest extends Specification { def "creates isolated managed interface"() { def instantiator = TestUtil.instantiatorFactory().inject() - def value = instantiator.newInstance(BeanInterface) - value.prop1 = "a" + def original = instantiator.newInstance(BeanInterface) + original.prop1 = "a" expect: - def isolated = snapshotter.isolate(value) - isolated instanceof IsolatedManagedTypeSnapshot + def isolated = snapshotter.isolate(original) + isolated instanceof IsolatedManagedValue def copy = isolated.isolate() - !copy.is(value) + !copy.is(original) copy.prop1 == "a" } @@ -609,7 +695,7 @@ class DefaultValueSnapshotterTest extends Specification { expect: def snapshot = snapshotter.snapshot(value) - snapshot instanceof ManagedTypeSnapshot + snapshot instanceof ManagedValueSnapshot snapshot == snapshotter.snapshot(value) snapshot == snapshotter.snapshot(value1) snapshot != snapshotter.snapshot(value2) @@ -618,14 +704,14 @@ class DefaultValueSnapshotterTest extends Specification { def "creates isolated managed abstract class"() { def instantiator = TestUtil.instantiatorFactory().inject() - def value = instantiator.newInstance(AbstractBean) - value.prop1 = "a" + def original = instantiator.newInstance(AbstractBean) + original.prop1 = "a" expect: - def isolated = snapshotter.isolate(value) - isolated instanceof IsolatedManagedTypeSnapshot + def isolated = snapshotter.isolate(original) + isolated instanceof IsolatedManagedValue def copy = isolated.isolate() - !copy.is(value) + !copy.is(original) copy.prop1 == "a" } @@ -636,13 +722,13 @@ class DefaultValueSnapshotterTest extends Specification { expect: def isolatedEmpty = snapshotter.isolate(empty) - isolatedEmpty instanceof IsolatedFileCollection + isolatedEmpty instanceof IsolatedManagedValue def copyEmpty = isolatedEmpty.isolate() !copyEmpty.is(empty) copyEmpty.files as List == [] def isolated = snapshotter.isolate(files1) - isolated instanceof IsolatedFileCollection + isolated instanceof IsolatedManagedValue def copy = isolated.isolate() !copy.is(files1) copy.files == files1.files @@ -660,7 +746,7 @@ class DefaultValueSnapshotterTest extends Specification { } def "creates isolated serializable type"() { - def value = new Bean(prop: "123") + def original = new Bean(prop: "123") def loader = new GroovyClassLoader(getClass().classLoader) loader.addURL(ClasspathUtil.getClasspathForClass(GroovyObject).toURI().toURL()) @@ -669,11 +755,11 @@ class DefaultValueSnapshotterTest extends Specification { assert cl.name == Bean.name expect: - def isolated = snapshotter.isolate(value) + def isolated = snapshotter.isolate(original) isolated instanceof SerializedValueSnapshot def other = isolated.isolate() other.prop == "123" - !other.is(value) + !other.is(original) def v = isolated.coerce(cl) v.prop == "123" @@ -917,12 +1003,9 @@ class DefaultValueSnapshotterTest extends Specification { } def "creates snapshot for provider type from candidate"() { - def value = Stub(Provider) - value.get() >> "123" - def value2 = Stub(Provider) - value2.get() >> "123" - def value3 = Stub(Provider) - value3.get() >> "12" + def value = Providers.of("123") + def value2 = Providers.of("123") + def value3 = Providers.of("12") expect: def snapshot = snapshotter.snapshot(value) diff --git a/subprojects/snapshots/src/test/groovy/org/gradle/internal/snapshot/impl/SnapshotSerializerTest.groovy b/subprojects/snapshots/src/test/groovy/org/gradle/internal/snapshot/impl/SnapshotSerializerTest.groovy index d8c4ca54ffad3..e3f663d7631b7 100644 --- a/subprojects/snapshots/src/test/groovy/org/gradle/internal/snapshot/impl/SnapshotSerializerTest.groovy +++ b/subprojects/snapshots/src/test/groovy/org/gradle/internal/snapshot/impl/SnapshotSerializerTest.groovy @@ -163,16 +163,8 @@ class SnapshotSerializerTest extends Specification { original == written } - def "serializes provider properties"() { - def original = new ProviderSnapshot(string("123")) - write(original) - - expect: - original == written - } - def "serializes managed type properties"() { - def original = new ManagedTypeSnapshot("named", integer(123)) + def original = new ManagedValueSnapshot("named", integer(123)) write(original) expect: diff --git a/subprojects/soak/soak.gradle b/subprojects/soak/soak.gradle deleted file mode 100644 index c8dfd41c007d1..0000000000000 --- a/subprojects/soak/soak.gradle +++ /dev/null @@ -1,32 +0,0 @@ -import org.gradle.gradlebuild.unittestandcompile.ModuleType - -plugins { - id 'gradlebuild.classycle' -} - -dependencies { - testFixturesApi project(':internalIntegTesting') -} - -gradlebuildJava { - moduleType = ModuleType.INTERNAL -} - -testFixtures { - from(':core') -} - -tasks.matching { it.name in [ 'integTest', 'java9IntegTest'] }.configureEach { - options { - excludeCategories 'org.gradle.soak.categories.SoakTest' - } -} - -tasks.register('soakTest', org.gradle.gradlebuild.test.integrationtests.SoakTest) { - testClassesDirs = sourceSets.integTest.output.classesDirs - classpath = sourceSets.integTest.runtimeClasspath - systemProperties['org.gradle.soaktest'] = 'true' - options { - includeCategories 'org.gradle.soak.categories.SoakTest' - } -} diff --git a/subprojects/soak/soak.gradle.kts b/subprojects/soak/soak.gradle.kts new file mode 100644 index 0000000000000..9e216bc79503e --- /dev/null +++ b/subprojects/soak/soak.gradle.kts @@ -0,0 +1,41 @@ +import org.gradle.gradlebuild.unittestandcompile.ModuleType + +plugins { + gradlebuild.classycle + `kotlin-library` +} + +dependencies { + testFixturesApi(project(":internalIntegTesting")) + testImplementation(project(":kotlinDslTestFixtures")) +} + +gradlebuildJava { + moduleType = ModuleType.INTERNAL +} + +testFixtures { + from(":core") +} + +tasks.integTest { + options { + require(this is JUnitOptions) + excludeCategories("org.gradle.soak.categories.SoakTest") + } +} + +tasks.register("soakTest", org.gradle.gradlebuild.test.integrationtests.SoakTest::class) { + val integTestSourceSet = sourceSets.integTest.get() + testClassesDirs = integTestSourceSet.output.classesDirs + classpath = integTestSourceSet.runtimeClasspath + systemProperty("org.gradle.soaktest", "true") + options { + require(this is JUnitOptions) + includeCategories("org.gradle.soak.categories.SoakTest") + } +} + +classycle { + excludePatterns.set(listOf("META-INF/*.kotlin_module")) +} diff --git a/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenCentralDependencyResolveIntegrationTest.groovy b/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenCentralDependencyResolveIntegrationTest.groovy new file mode 100644 index 0000000000000..c6ee64a2bd8e5 --- /dev/null +++ b/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenCentralDependencyResolveIntegrationTest.groovy @@ -0,0 +1,79 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.gradle.connectivity + +import org.gradle.integtests.fixtures.AbstractIntegrationSpec +import org.gradle.util.Requires +import org.gradle.util.TestPrecondition + +@Requires(TestPrecondition.ONLINE) +class MavenCentralDependencyResolveIntegrationTest extends AbstractIntegrationSpec { + def "resolves a minimal dependency from Maven Central"() { + given: + buildFile << """ +repositories { + mavenCentral() + mavenCentral { // just test this syntax works. + name = "otherCentral" + content { + includeGroup 'org.test' + } + } +} + +configurations { + compile +} + +dependencies { + compile "ch.qos.logback:logback-classic:1.0.13" +} + +task check { + doLast { + def compile = configurations.compile + assert compile.resolvedConfiguration.firstLevelModuleDependencies.collect { it.name } == [ + 'ch.qos.logback:logback-classic:1.0.13', + ] + + assert compile.collect { it.name } == [ + 'logback-classic-1.0.13.jar', + 'logback-core-1.0.13.jar', + 'slf4j-api-1.7.5.jar' + ] + + assert compile.resolvedConfiguration.resolvedArtifacts.collect { it.file.name } == [ + 'logback-classic-1.0.13.jar', + 'logback-core-1.0.13.jar', + 'slf4j-api-1.7.5.jar' + ] + } +} + +task repoNames { + doLast { + println repositories*.name + } +} +""" + + expect: + succeeds "check", "repoNames" + + and: + output.contains(["MavenRepo", "otherCentral"].toString()) + } +} diff --git a/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenGoogleDependencyResolveIntegrationTest.groovy b/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenGoogleDependencyResolveIntegrationTest.groovy index 61ad6ec70940f..7618890231684 100644 --- a/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenGoogleDependencyResolveIntegrationTest.groovy +++ b/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenGoogleDependencyResolveIntegrationTest.groovy @@ -27,6 +27,12 @@ class MavenGoogleDependencyResolveIntegrationTest extends AbstractDependencyReso buildFile << """ repositories { google() + google { // just test this syntax works. + name = "otherGoogle" + content { + includeGroup 'org.sample' + } + } } """ } @@ -36,7 +42,7 @@ class MavenGoogleDependencyResolveIntegrationTest extends AbstractDependencyReso buildFile << """ task checkRepoName { doLast { - assert repositories*.name == ['Google'] + assert repositories*.name == ['Google', 'otherGoogle'] } } """ diff --git a/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenJcenterDependencyResolveIntegrationTest.groovy b/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenJcenterDependencyResolveIntegrationTest.groovy index 48be7b4592f0f..c24bb5b4ea58c 100644 --- a/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenJcenterDependencyResolveIntegrationTest.groovy +++ b/subprojects/soak/src/integTest/groovy/org/gradle/connectivity/MavenJcenterDependencyResolveIntegrationTest.groovy @@ -28,6 +28,9 @@ repositories { jcenter() jcenter { // just test this syntax works. name = "otherJcenter" + content { + includeGroup 'org.sample' + } } } diff --git a/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/ClassLoaderLeakAvoidanceSoakTest.groovy b/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/ClassLoaderLeakAvoidanceSoakTest.groovy index cfb5b10a930b5..c5e8e70d9fbe1 100644 --- a/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/ClassLoaderLeakAvoidanceSoakTest.groovy +++ b/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/ClassLoaderLeakAvoidanceSoakTest.groovy @@ -94,7 +94,7 @@ class ClassLoaderLeakAvoidanceSoakTest extends AbstractIntegrationSpec { expect: for(int i = 0; i < 35; i++) { buildFile.text = buildFile.text.replace("Foo$i", "Foo${i + 1}") - executer.withBuildJvmOpts("-Xmx128m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:MaxMetaspaceSize=64m") + executer.withBuildJvmOpts("-Xms64m", "-Xmx128m", "-XX:+HeapDumpOnOutOfMemoryError", "-XX:MaxMetaspaceSize=64m") assert succeeds("myTask") } } diff --git a/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringCoverage.groovy b/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringCoverage.groovy index 619396cfadeec..8fbe5954b5ade 100644 --- a/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringCoverage.groovy +++ b/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringCoverage.groovy @@ -18,7 +18,7 @@ package org.gradle.launcher.daemon import org.gradle.launcher.daemon.fixtures.FullyQualifiedGarbageCollector -import static org.gradle.launcher.daemon.fixtures.JavaGarbageCollector.* +import static org.gradle.integtests.fixtures.daemon.JavaGarbageCollector.* import static org.gradle.launcher.daemon.fixtures.JdkVendor.* class DaemonPerformanceMonitoringCoverage { diff --git a/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringSoakTest.groovy b/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringSoakTest.groovy index b9b8f7f2273a5..85249155c4c59 100644 --- a/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringSoakTest.groovy +++ b/subprojects/soak/src/integTest/groovy/org/gradle/launcher/daemon/DaemonPerformanceMonitoringSoakTest.groovy @@ -17,32 +17,16 @@ package org.gradle.launcher.daemon import org.gradle.integtests.fixtures.TargetCoverage -import org.gradle.integtests.fixtures.executer.ExecutionFailure import org.gradle.integtests.fixtures.executer.GradleHandle import org.gradle.launcher.daemon.fixtures.DaemonMultiJdkIntegrationTest -import org.gradle.launcher.daemon.fixtures.JdkVendor -import org.gradle.launcher.daemon.server.DaemonStateCoordinator -import org.gradle.launcher.daemon.server.api.DaemonStoppedException import org.gradle.launcher.daemon.server.health.DaemonMemoryStatus -import org.gradle.launcher.daemon.server.health.GcThrashingDaemonExpirationStrategy import org.gradle.soak.categories.SoakTest -import org.gradle.test.fixtures.ConcurrentTestUtil import org.junit.experimental.categories.Category -import spock.lang.Unroll - -import static org.junit.Assume.assumeTrue @Category(SoakTest) @TargetCoverage({DaemonPerformanceMonitoringCoverage.ALL_VERSIONS}) class DaemonPerformanceMonitoringSoakTest extends DaemonMultiJdkIntegrationTest { - int maxBuilds - String heapSize - int leakRate - Closure setupBuildScript - def setup() { - buildFile << "${logJdk()}" - // Set JVM args for GC String jvmArgs = "" if (file('gradle.properties').exists()) { @@ -54,132 +38,25 @@ class DaemonPerformanceMonitoringSoakTest extends DaemonMultiJdkIntegrationTest ) } - @Unroll - def "when build leaks slowly daemon is eventually expired (heap: #heap)"() { + def "when build leaks slowly daemon is eventually expired"() { when: - setupBuildScript = tenuredHeapLeak - maxBuilds = builds - heapSize = heap - leakRate = rate - + setupTenuredHeapLeak(leakRate) then: - daemonIsExpiredEagerly() + daemonIsExpiredEagerly(maxBuilds, heapSize) where: - builds | heap | rate - 45 | "200m" | 600 - 40 | "1024m" | 4000 - } - - def "when build leaks within available memory the daemon is not expired"() { - when: - setupBuildScript = tenuredHeapLeak - maxBuilds = 20 - heapSize = "500m" - leakRate = 300 - - then: - !daemonIsExpiredEagerly() - } - - def "greedy build with no leak does not expire daemon"() { - when: - setupBuildScript = greedyBuildNoLeak - maxBuilds = 20 - heapSize = "200m" - leakRate = 3800 - - then: - !daemonIsExpiredEagerly() - } - - def "when leak occurs while daemon is idle daemon is still expired"() { - // This is so the idle timeout expiration strategy doesn't kick in - // before the gc monitoring expires the daemon - executer.withDaemonIdleTimeoutSecs(300) - heapSize = "200m" - leakRate = 1000 - - when: - leaksWhenIdle() - executer.withArguments("-Dorg.gradle.daemon.healthcheckinterval=1000") - executer.withBuildJvmOpts("-D${DaemonMemoryStatus.ENABLE_PERFORMANCE_MONITORING}=true", "-Xmx${heapSize}", "-Dorg.gradle.daemon.performance.logging=true") - executer.noExtraLogging() - run() - - then: - daemons.daemon.assertIdle() - - and: - String logText = daemons.daemon.log - ConcurrentTestUtil.poll(30) { - println daemons.daemon.log - logText - logText = daemons.daemon.log - daemons.daemon.assertStopped() - } - - and: - daemons.daemon.log.contains(DaemonStateCoordinator.DAEMON_WILL_STOP_MESSAGE) + maxBuilds | heapSize | leakRate + 45 | "200m" | 600 + 40 | "1024m" | 4000 } - def "when build leaks permgen space daemon is expired"() { - assumeTrue(version.vendor != JdkVendor.IBM && version.version == "1.7") - - when: - setupBuildScript = permGenLeak - maxBuilds = 20 - heapSize = "200m" - leakRate = 3300 - - then: - daemonIsExpiredEagerly() - } - - def "detects a thrashing condition" () { - // This is so the idle timeout expiration strategy doesn't kick in - // before the gc monitoring expires the daemon - executer.withDaemonIdleTimeoutSecs(300) - heapSize = "200m" - leakRate = 1700 - - when: - leaksWithinOneBuild() - executer.withArguments("-Dorg.gradle.daemon.healthcheckinterval=1000", "--debug") - executer.withBuildJvmOpts("-D${DaemonMemoryStatus.ENABLE_PERFORMANCE_MONITORING}=true", "-Xmx${heapSize}", "-Dorg.gradle.daemon.performance.logging=true") - executer.noExtraLogging() - GradleHandle gradle = executer.start() - - then: - ConcurrentTestUtil.poll(10) { - daemons.daemon.assertBusy() - } - - when: - file("leak").createFile() - - then: - ConcurrentTestUtil.poll(60) { - daemons.daemon.assertStopped() - } - - and: - daemons.daemon.log.contains(DaemonStateCoordinator.DAEMON_STOPPING_IMMEDIATELY_MESSAGE) - - when: - ExecutionFailure failure = gradle.waitForFailure() - - then: - failure.assertHasDescription(DaemonStoppedException.MESSAGE + ": " + GcThrashingDaemonExpirationStrategy.EXPIRATION_REASON) - } - - private boolean daemonIsExpiredEagerly() { + private boolean daemonIsExpiredEagerly(int maxBuilds, String heapSize) { def dataFile = file("stats") - setupBuildScript() int newDaemons = 0 try { for (int i = 0; i < maxBuilds; i++) { executer.noExtraLogging() - executer.withBuildJvmOpts("-D${DaemonMemoryStatus.ENABLE_PERFORMANCE_MONITORING}=true", "-Xmx${heapSize}", "-Dorg.gradle.daemon.performance.logging=true") + executer.withBuildJvmOpts("-D${DaemonMemoryStatus.ENABLE_PERFORMANCE_MONITORING}=true", "-Xms128m", "-XX:MaxMetaspaceSize=${heapSize}", "-Xmx${heapSize}", "-Dorg.gradle.daemon.performance.logging=true") GradleHandle gradle = executer.start() gradle.waitForExit() if (gradle.standardOutput ==~ /(?s).*Starting build in new daemon \[memory: [0-9].*/) { @@ -199,126 +76,32 @@ class DaemonPerformanceMonitoringSoakTest extends DaemonMultiJdkIntegrationTest return false } - private final Closure tenuredHeapLeak = { - buildFile << """ - class State { - static int x - static map = [:] - } - State.x++ - - //simulate normal collectible objects - 5000.times { - State.map.put(it, "foo" * ${leakRate}) - } - - //simulate the leak - 1000.times { - State.map.put(UUID.randomUUID(), "foo" * ${leakRate}) - } - - println "Build: " + State.x - """ - } - - private final Closure greedyBuildNoLeak = { + private final void setupTenuredHeapLeak(int leakRate) { buildFile << """ - Map map = [:] + logger.warn("Build is running with JDK: " + System.getProperty('java.home')) - //simulate normal collectible objects - 5000.times { - map.put(it, "foo" * ${leakRate}) - } - """ - } - - private final Closure leaksWhenIdle = { - buildFile << """ class State { static int x static map = [:] } - State.x++ - - new Thread().start { - while (true) { - logger.warn "leaking some heap" + try { + State.x++ - //simulate normal collectible objects - 5000.times { - State.map.put(it, "foo" * ${leakRate}) - } - - //simulate the leak - 1000.times { - State.map.put(UUID.randomUUID(), "foo" * ${leakRate}) - } - sleep(750) + //simulate normal collectible objects + 5000.times { + State.map.put(it, "foo" * ${leakRate}) } - } - """ - } - - private final Closure permGenLeak = { - leakRate.times { - file("buildSrc/src/main/java/Generated${it}.java") << """ - public class Generated${it} { } - """ - } - buildFile << """ - import java.net.URLClassLoader - class State { - static int x - static map = [:] - } - State.x++ - - //simulate normal perm gen usage - 5.times { - ClassLoader classLoader1 = new URLClassLoader(buildscript.classLoader.URLs) - ${leakRate}.times { - classLoader1.loadClass("Generated\${it}") + //simulate the leak + 1000.times { + State.map.put(UUID.randomUUID(), "foo" * ${leakRate}) } - State.map.put("CL${it}", classLoader1) - } - //simulate the leak - ClassLoader classLoader2 = new URLClassLoader(buildscript.classLoader.URLs) - ${leakRate}.times { - classLoader2.loadClass("Generated\${it}") + println "Build: " + State.x + } catch(OutOfMemoryError e) { + // TeamCity recognizes this message as build failures if it occurs in build log + throw new OutOfMemoryError(e?.message?.replace(' ', '_')) } - State.map.put(UUID.randomUUID(), classLoader2) - - println "Build: " + State.x """ } - - private final Closure leaksWithinOneBuild = { - buildFile << """ - def map = [:] - - while (true) { - if (file("leak").exists()) { - logger.debug "leaking some heap" - //simulate normal collectible objects - 10000.times { - map.put(it, "foo" * ${leakRate}) - } - - //simulate the leak - 1000.times { - map.put(UUID.randomUUID(), "foo" * ${leakRate}) - } - } else { - logger.warn "waiting for leak to start" - } - sleep 1000 - } - """ - } - - String logJdk() { - return """logger.warn("Build is running with JDK: \${System.getProperty('java.home')}")""" - } } diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/AbstractScriptCachingIntegrationTest.kt b/subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/AbstractScriptCachingIntegrationTest.kt similarity index 100% rename from subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/AbstractScriptCachingIntegrationTest.kt rename to subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/AbstractScriptCachingIntegrationTest.kt diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/BuildCacheIntegrationTest.kt b/subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/BuildCacheIntegrationTest.kt similarity index 96% rename from subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/BuildCacheIntegrationTest.kt rename to subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/BuildCacheIntegrationTest.kt index f6d5582b2bab6..35431225bc66b 100644 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/BuildCacheIntegrationTest.kt +++ b/subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/BuildCacheIntegrationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,14 +18,18 @@ package org.gradle.kotlin.dsl.caching import org.gradle.kotlin.dsl.fixtures.normalisedPath +import org.gradle.soak.categories.SoakTest + import org.hamcrest.CoreMatchers.containsString import org.hamcrest.MatcherAssert.assertThat import org.junit.Test +import org.junit.experimental.categories.Category import java.io.File +@Category(SoakTest::class) class BuildCacheIntegrationTest : AbstractScriptCachingIntegrationTest() { @Test diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/CompilationCache.kt b/subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/CompilationCache.kt similarity index 99% rename from subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/CompilationCache.kt rename to subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/CompilationCache.kt index 958edccc9f32c..2f0b624364eb2 100644 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/CompilationCache.kt +++ b/subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/CompilationCache.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/ScriptCachingIntegrationTest.kt b/subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/ScriptCachingIntegrationTest.kt similarity index 97% rename from subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/ScriptCachingIntegrationTest.kt rename to subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/ScriptCachingIntegrationTest.kt index 2a3d9e083892c..2062a21365153 100644 --- a/subprojects/kotlin-dsl/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/ScriptCachingIntegrationTest.kt +++ b/subprojects/soak/src/integTest/kotlin/org/gradle/kotlin/dsl/caching/ScriptCachingIntegrationTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2018 the original author or authors. + * Copyright 2019 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,15 +25,17 @@ import org.gradle.kotlin.dsl.execution.ProgramSource import org.gradle.kotlin.dsl.execution.ProgramTarget import org.gradle.kotlin.dsl.fixtures.DeepThought -import org.junit.Ignore + +import org.gradle.soak.categories.SoakTest import org.junit.Test -import spock.lang.Issue +import org.junit.experimental.categories.Category import java.io.File import java.util.UUID +@Category(SoakTest::class) class ScriptCachingIntegrationTest : AbstractScriptCachingIntegrationTest() { @Test @@ -217,8 +219,6 @@ class ScriptCachingIntegrationTest : AbstractScriptCachingIntegrationTest() { } @Test - @Ignore - @Issue("https://github.com/gradle/gradle-private/issues/1801") fun `in-memory script class loading cache releases memory of unused entries`() { // given: buildSrc memory hog @@ -227,14 +227,14 @@ class ScriptCachingIntegrationTest : AbstractScriptCachingIntegrationTest() { import org.gradle.api.tasks.* class MyTask extends DefaultTask { - static final byte[] MEMORY_HOG = new byte[64 * 1024 * 1024] + static final byte[][] MEMORY_HOG = new byte[1024][1024 * 64] @TaskAction void runAction0() {} } """) val settingsFile = cachedSettingsFile(withSettings(""), false, false) val buildFile = cachedBuildFile(withBuildScript("""task("myTask")"""), true) - // expect: + // expect: memory hog released for (run in 1..4) { myTask.writeText(myTask.readText().replace("runAction${run - 1}", "runAction$run")) buildWithDaemonHeapSize(256, "myTask").apply { diff --git a/subprojects/soak/src/testFixtures/groovy/org/gradle/launcher/daemon/fixtures/FullyQualifiedGarbageCollector.groovy b/subprojects/soak/src/testFixtures/groovy/org/gradle/launcher/daemon/fixtures/FullyQualifiedGarbageCollector.groovy index 831bb484990ee..66192ee07c922 100644 --- a/subprojects/soak/src/testFixtures/groovy/org/gradle/launcher/daemon/fixtures/FullyQualifiedGarbageCollector.groovy +++ b/subprojects/soak/src/testFixtures/groovy/org/gradle/launcher/daemon/fixtures/FullyQualifiedGarbageCollector.groovy @@ -17,6 +17,7 @@ package org.gradle.launcher.daemon.fixtures import org.gradle.api.JavaVersion +import org.gradle.integtests.fixtures.daemon.JavaGarbageCollector class FullyQualifiedGarbageCollector implements Comparable { JdkVendor vendor diff --git a/subprojects/testing-junit-platform/src/main/java/org/gradle/api/internal/tasks/testing/junitplatform/JUnitPlatformTestExecutionListener.java b/subprojects/testing-junit-platform/src/main/java/org/gradle/api/internal/tasks/testing/junitplatform/JUnitPlatformTestExecutionListener.java index 5c8f0faa6277a..66f68af23fd8d 100644 --- a/subprojects/testing-junit-platform/src/main/java/org/gradle/api/internal/tasks/testing/junitplatform/JUnitPlatformTestExecutionListener.java +++ b/subprojects/testing-junit-platform/src/main/java/org/gradle/api/internal/tasks/testing/junitplatform/JUnitPlatformTestExecutionListener.java @@ -121,7 +121,9 @@ private void reportStartedUnlessAlreadyStarted(TestIdentifier testIdentifier) { } private void reportSkipped(TestIdentifier testIdentifier) { - currentTestPlan.getChildren(testIdentifier).forEach(child -> executionSkipped(child)); + currentTestPlan.getChildren(testIdentifier).stream() + .filter(child -> !wasStarted(child)) + .forEach(child -> executionSkipped(child)); if (testIdentifier.isTest()) { resultProcessor.completed(getId(testIdentifier), completeEvent(SKIPPED)); } else if (isClass(testIdentifier)) { diff --git a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/jvm/test/JUnitComponentUnderTestIntegrationTest.groovy b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/jvm/test/JUnitComponentUnderTestIntegrationTest.groovy index 18c9e31c3ac39..427dd84b8f4c9 100644 --- a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/jvm/test/JUnitComponentUnderTestIntegrationTest.groovy +++ b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/jvm/test/JUnitComponentUnderTestIntegrationTest.groovy @@ -159,22 +159,22 @@ class JUnitComponentUnderTestIntegrationTest extends AbstractJUnitTestExecutionI greeterTestCase() when: - succeeds ':myTestGreeterJava6JarBinaryTest' - def result = new DefaultTestExecutionResult(testDirectory, 'build', 'myTest', 'greeterJava6Jar') + succeeds ':myTestGreeterJava7JarBinaryTest' + def result = new DefaultTestExecutionResult(testDirectory, 'build', 'myTest', 'greeterJava7Jar') then: - executedAndNotSkipped ':myTestGreeterJava6JarBinaryTest', ':compileGreeterJava6JarGreeterJava' + executedAndNotSkipped ':myTestGreeterJava7JarBinaryTest', ':compileGreeterJava7JarGreeterJava' result.assertTestClassesExecuted('com.acme.GreeterTest') result.testClass('com.acme.GreeterTest') .assertTestCount(1, 0, 0) .assertTestsExecuted('testGreeting') when: - succeeds ':myTestGreeterJava7JarBinaryTest' - result = new DefaultTestExecutionResult(testDirectory, 'build', 'myTest', 'greeterJava7Jar') + succeeds ':myTestGreeterJava8JarBinaryTest' + result = new DefaultTestExecutionResult(testDirectory, 'build', 'myTest', 'greeterJava8Jar') then: - executedAndNotSkipped ':myTestGreeterJava7JarBinaryTest', ':compileGreeterJava7JarGreeterJava' + executedAndNotSkipped ':myTestGreeterJava8JarBinaryTest', ':compileGreeterJava8JarGreeterJava' result.assertTestClassesExecuted('com.acme.GreeterTest') result.testClass('com.acme.GreeterTest') .assertTestCount(1, 0, 0) @@ -283,8 +283,8 @@ class JUnitComponentUnderTestIntegrationTest extends AbstractJUnitTestExecutionI model { components { greeter { - targetPlatform 'java6' targetPlatform 'java7' + targetPlatform 'java8' } } } diff --git a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/fixture/AbstractJvmFailFastIntegrationSpec.groovy b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/fixture/AbstractJvmFailFastIntegrationSpec.groovy index 3080d52633c50..624f30e6f3bdc 100644 --- a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/fixture/AbstractJvmFailFastIntegrationSpec.groovy +++ b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/fixture/AbstractJvmFailFastIntegrationSpec.groovy @@ -158,8 +158,8 @@ abstract class AbstractJvmFailFastIntegrationSpec extends AbstractIntegrationSpe then: ConcurrentTestUtil.poll { - assert gradleHandle.standardOutput.contains(workInProgressLine('> :test > Executing test pkg.FailedTest')) - assert gradleHandle.standardOutput.contains(workInProgressLine('> :test > Executing test pkg.OtherTest')) + assertHasWorkInProgress(gradleHandle, '> :test > Executing test pkg.FailedTest') + assertHasWorkInProgress(gradleHandle, '> :test > Executing test pkg.OtherTest') } testExecution.release(FAILED_RESOURCE) diff --git a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/fixture/JUnitCoverage.groovy b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/fixture/JUnitCoverage.groovy index a4ac8a4a29f1c..80f54e3c51eb2 100644 --- a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/fixture/JUnitCoverage.groovy +++ b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/fixture/JUnitCoverage.groovy @@ -25,8 +25,8 @@ import org.gradle.api.JavaVersion */ class JUnitCoverage { final static String NEWEST = '4.12' - final static String LATEST_JUPITER_VERSION = '5.3.1' - final static String LATEST_VINTAGE_VERSION = '5.3.1' + final static String LATEST_JUPITER_VERSION = '5.4.0' + final static String LATEST_VINTAGE_VERSION = '5.4.0' final static String JUPITER = 'Jupiter:' + LATEST_JUPITER_VERSION final static String VINTAGE = 'Vintage:' + LATEST_VINTAGE_VERSION final static String[] LARGE_COVERAGE = ['4.0', '4.4', '4.8.2', NEWEST] diff --git a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest.groovy b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest.groovy new file mode 100644 index 0000000000000..b4cb252327d73 --- /dev/null +++ b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest.groovy @@ -0,0 +1,53 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.testing.junit + +import org.gradle.integtests.fixtures.DefaultTestExecutionResult +import org.gradle.integtests.fixtures.TargetCoverage +import org.gradle.integtests.fixtures.TestResources +import org.gradle.testing.fixture.JUnitMultiVersionIntegrationSpec +import org.junit.Rule + +import static org.gradle.testing.fixture.JUnitCoverage.JUNIT_VINTAGE + +@TargetCoverage({ JUNIT_VINTAGE }) +class JUnitAbortedTestClassIntegrationTest extends JUnitMultiVersionIntegrationSpec { + + @Rule TestResources resources = new TestResources(temporaryFolder) + + def supportsAssumptionsInRules() { + given: + executer.noExtraLogging() + buildFile << """ +dependencies { + testCompile '$dependencyNotation' +} +""" + + when: + run('test') + + then: + def result = new DefaultTestExecutionResult(testDirectory) + result.assertTestClassesExecuted('org.gradle.SkippingRuleTests') + result.testClass('org.gradle.SkippingRuleTests') + .assertTestCount(3, 0, 0) + .assertTestsExecuted('a') + .assertTestsSkipped('b', 'c') + } + +} diff --git a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec.groovy b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec.groovy index 7b8171aa1c859..a289123cd6e9b 100644 --- a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec.groovy +++ b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec.groovy @@ -32,15 +32,10 @@ class JUnitCategoriesCoverageIntegrationSpec extends JUnitMultiVersionIntegratio def setup() { executer.noExtraLogging() - } - - def configureJUnit() { buildFile << "dependencies { testCompile '$dependencyNotation' }" } def canSpecifyIncludeAndExcludeCategories() { - configureJUnit() - when: run('test') @@ -59,8 +54,6 @@ class JUnitCategoriesCoverageIntegrationSpec extends JUnitMultiVersionIntegratio } def canSpecifyExcludesOnly() { - configureJUnit() - when: run('test') @@ -76,8 +69,6 @@ class JUnitCategoriesCoverageIntegrationSpec extends JUnitMultiVersionIntegratio } def canCombineCategoriesWithCustomRunner() { - configureJUnit() - when: run('test') @@ -87,4 +78,20 @@ class JUnitCategoriesCoverageIntegrationSpec extends JUnitMultiVersionIntegratio result.testClass("org.gradle.SomeLocaleTests").assertTestCount(3, 0, 0) result.testClass("org.gradle.SomeLocaleTests").assertTestsExecuted('ok1 [de]', 'ok1 [en]', 'ok1 [fr]') } + + def canRunParameterizedTestsWithCategories() { + when: + run('test') + + then: + def expectedTestClasses = ['org.gradle.NestedTestsWithCategories$TagOnMethodNoParam', 'org.gradle.NestedTestsWithCategories$TagOnMethod'] + if (isVintage() || !(version in ['4.10', '4.11', '4.12'])) { + expectedTestClasses << 'org.gradle.NestedTestsWithCategories$TagOnClass' + } + DefaultTestExecutionResult result = new DefaultTestExecutionResult(testDirectory) + result.assertTestClassesExecuted(expectedTestClasses as String[]) + expectedTestClasses.each { + result.testClass(it).assertTestCount(1, 0, 0) + } + } } diff --git a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junitplatform/JUnitPlatformIntegrationTest.groovy b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junitplatform/JUnitPlatformIntegrationTest.groovy index 79436cf679c15..3ef02086d486a 100644 --- a/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junitplatform/JUnitPlatformIntegrationTest.groovy +++ b/subprojects/testing-jvm/src/integTest/groovy/org/gradle/testing/junitplatform/JUnitPlatformIntegrationTest.groovy @@ -20,6 +20,7 @@ import org.gradle.integtests.fixtures.DefaultTestExecutionResult import org.gradle.util.Requires import org.gradle.util.TestPrecondition import spock.lang.Issue +import spock.lang.Timeout import spock.lang.Unroll import static org.gradle.testing.fixture.JUnitCoverage.LATEST_JUPITER_VERSION @@ -366,6 +367,7 @@ public class StaticInnerTest { 'excludeEngines' | '"junit-jupiter"' } + @Timeout(60) @Issue('https://github.com/gradle/gradle/issues/6453') def "can handle parallel test execution"() { given: @@ -383,8 +385,11 @@ public class StaticInnerTest { import java.util.concurrent.*; import org.junit.jupiter.api.*; + import org.junit.jupiter.api.parallel.*; import static org.junit.jupiter.api.Assertions.*; + import static org.junit.jupiter.api.parallel.ExecutionMode.CONCURRENT; + @Execution(CONCURRENT) class Sync { static CountDownLatch LATCH = new CountDownLatch($numTestClasses); } diff --git a/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest/supportsAssumptionsInRules/build.gradle b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest/supportsAssumptionsInRules/build.gradle new file mode 100644 index 0000000000000..1c9cc4d8975c9 --- /dev/null +++ b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest/supportsAssumptionsInRules/build.gradle @@ -0,0 +1,18 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +apply plugin: 'java' +repositories { mavenCentral() } diff --git a/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest/supportsAssumptionsInRules/src/test/java/org/gradle/SkippingRuleTests.java b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest/supportsAssumptionsInRules/src/test/java/org/gradle/SkippingRuleTests.java new file mode 100644 index 0000000000000..cb76679cfdc60 --- /dev/null +++ b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitAbortedTestClassIntegrationTest/supportsAssumptionsInRules/src/test/java/org/gradle/SkippingRuleTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle; + +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.MethodRule; + +import static org.junit.Assume.assumeFalse; +import static org.junit.runners.MethodSorters.NAME_ASCENDING; + +@FixMethodOrder(NAME_ASCENDING) +public class SkippingRuleTests { + + @Rule + public MethodRule misbehavingSkippingRule = (statement, method, target) -> { + assumeFalse(method.getName().equals("b")); + return statement; + }; + + @Test + public void a() { + } + + @Test + public void b() { + } + + @Test + public void c() { + } + +} diff --git a/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/build.gradle b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/build.gradle new file mode 100644 index 0000000000000..987aa0bd0c1a0 --- /dev/null +++ b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/build.gradle @@ -0,0 +1,11 @@ +apply plugin: "java" + +repositories { + mavenCentral() +} + +test { + useJUnit { + includeCategories 'org.gradle.SomeCategory' + } +} diff --git a/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/src/test/java/org/gradle/NestedTestsWithCategories.java b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/src/test/java/org/gradle/NestedTestsWithCategories.java new file mode 100644 index 0000000000000..c633a47aa4814 --- /dev/null +++ b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/src/test/java/org/gradle/NestedTestsWithCategories.java @@ -0,0 +1,96 @@ +package org.gradle; + +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.ArrayList; + +import static org.junit.Assert.fail; + +public class NestedTestsWithCategories { + + @Category(SomeCategory.class) + @RunWith(Parameterized.class) + public static class TagOnClass { + @Parameterized.Parameters + public static Iterable getParameters() { + ArrayList parameters = new ArrayList<>(); + parameters.add(new Object[] { "tag on class" }); + return parameters; + } + + private final String param; + + public TagOnClass(String param) { + this.param = param; + } + + @Test + public void run() { + System.err.println("executed " + param); + } + } + + @RunWith(Parameterized.class) + public static class TagOnMethod { + @Parameterized.Parameters + public static Iterable getParameters() { + ArrayList parameters = new ArrayList<>(); + parameters.add(new Object[] { "tag on method" }); + return parameters; + } + + private final String param; + + public TagOnMethod(String param) { + this.param = param; + } + + @Test + @Category(SomeCategory.class) + public void run() { + System.err.println("executed " + param); + } + + @Test + public void filteredOut() { + throw new AssertionError("should be filtered out"); + } + } + + public static class TagOnMethodNoParam { + @Test + @Category(SomeCategory.class) + public void run() { + System.err.println("executed tag on method (no param)"); + } + + @Test + public void filteredOut() { + throw new AssertionError("should be filtered out"); + } + } + + @RunWith(Parameterized.class) + public static class Untagged { + @Parameterized.Parameters + public static Iterable getParameters() { + ArrayList parameters = new ArrayList<>(); + parameters.add(new Object[] { "untagged" }); + return parameters; + } + + private final String param; + + public Untagged(String param) { + this.param = param; + } + + @Test + public void run() { + System.err.println("executed " + param); + } + } +} diff --git a/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/src/test/java/org/gradle/SomeCategory.java b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/src/test/java/org/gradle/SomeCategory.java new file mode 100644 index 0000000000000..036456a607fb4 --- /dev/null +++ b/subprojects/testing-jvm/src/integTest/resources/org/gradle/testing/junit/JUnitCategoriesCoverageIntegrationSpec/canRunParameterizedTestsWithCategories/src/test/java/org/gradle/SomeCategory.java @@ -0,0 +1,4 @@ +package org.gradle; + +public interface SomeCategory { +} diff --git a/subprojects/testing-jvm/src/main/java/org/gradle/api/internal/tasks/testing/junit/CategoryFilter.java b/subprojects/testing-jvm/src/main/java/org/gradle/api/internal/tasks/testing/junit/CategoryFilter.java index ebf57b91946b7..0d21368767135 100644 --- a/subprojects/testing-jvm/src/main/java/org/gradle/api/internal/tasks/testing/junit/CategoryFilter.java +++ b/subprojects/testing-jvm/src/main/java/org/gradle/api/internal/tasks/testing/junit/CategoryFilter.java @@ -43,8 +43,8 @@ class CategoryFilter extends Filter { public boolean shouldRun(final Description description) { Class testClass = description.getTestClass(); verifyCategories(testClass); - Description desc = description.isSuite() || testClass == null ? null : Description.createSuiteDescription(testClass); - return shouldRun(description, desc); + Description parent = description.isSuite() || testClass == null ? null : Description.createSuiteDescription(testClass); + return shouldRun(description, parent); } private void verifyCategories(Class testClass) { @@ -60,6 +60,18 @@ private void verifyCategories(Class testClass) { } private boolean shouldRun(final Description description, final Description parent) { + if (hasCorrectCategoryAnnotation(description, parent)) { + return true; + } + for (Description each : description.getChildren()) { + if (shouldRun(each, description)) { + return true; + } + } + return false; + } + + private boolean hasCorrectCategoryAnnotation(Description description, Description parent) { final Set> categories = new HashSet>(); Category annotation = description.getAnnotation(Category.class); if (annotation != null) { diff --git a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/xctest/AbstractSwiftXCTestComponentIntegrationTest.groovy b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/xctest/AbstractSwiftXCTestComponentIntegrationTest.groovy index fb12eb2f3d09a..3cf47452cc398 100644 --- a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/xctest/AbstractSwiftXCTestComponentIntegrationTest.groovy +++ b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/xctest/AbstractSwiftXCTestComponentIntegrationTest.groovy @@ -18,8 +18,10 @@ package org.gradle.nativeplatform.test.xctest import org.gradle.internal.os.OperatingSystem import org.gradle.language.swift.AbstractSwiftComponentIntegrationTest +import org.gradle.nativeplatform.fixtures.app.SourceElement import org.gradle.nativeplatform.fixtures.app.Swift3XCTest import org.gradle.nativeplatform.fixtures.app.Swift4XCTest +import org.gradle.nativeplatform.fixtures.app.Swift5XCTest import org.gradle.nativeplatform.fixtures.app.XCTestSourceElement import org.junit.Assume @@ -70,6 +72,11 @@ abstract class AbstractSwiftXCTestComponentIntegrationTest extends AbstractSwift return new Swift4XCTest('project') } + @Override + SourceElement getSwift5Component() { + return new Swift5XCTest('project') + } + @Override List getTasksToAssembleDevelopmentBinaryOfComponentUnderTest() { return [":compileTestSwift", ":linkTest", ":installTest", ":xcTest"] diff --git a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/xctest/AbstractSwiftXCTestComponentWithTestedComponentIntegrationTest.groovy b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/xctest/AbstractSwiftXCTestComponentWithTestedComponentIntegrationTest.groovy index 20f373f4a526b..1d31bcf2d98f1 100644 --- a/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/xctest/AbstractSwiftXCTestComponentWithTestedComponentIntegrationTest.groovy +++ b/subprojects/testing-native/src/integTest/groovy/org/gradle/nativeplatform/test/xctest/AbstractSwiftXCTestComponentWithTestedComponentIntegrationTest.groovy @@ -21,9 +21,13 @@ import org.gradle.nativeplatform.fixtures.RequiresInstalledToolChain import org.gradle.nativeplatform.fixtures.ToolChainRequirement import org.gradle.nativeplatform.fixtures.app.Swift3WithSwift4XCTest import org.gradle.nativeplatform.fixtures.app.Swift4WithSwift3XCTest +import org.gradle.nativeplatform.fixtures.app.Swift5WithSwift4XCTest import spock.lang.Unroll +import static org.junit.Assume.assumeTrue + abstract class AbstractSwiftXCTestComponentWithTestedComponentIntegrationTest extends AbstractSwiftXCTestComponentIntegrationTest implements XCTestExecutionResult { + // TODO: This test can be generalized so it's not opinionated on Swift 4.x but could also work on Swift 5.x @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_4) def "take swift source compatibility from tested component"() { given: @@ -53,10 +57,11 @@ abstract class AbstractSwiftXCTestComponentWithTestedComponentIntegrationTest ex swift3Component.assertTestCasesRan(testExecutionResult) } - @RequiresInstalledToolChain(ToolChainRequirement.SWIFTC_4) @Unroll def "honors Swift source compatibility difference on both tested component (#componentSourceCompatibility) and XCTest component (#xctestSourceCompatibility)"() { given: + assumeSwiftCompilerSupportsLanguageVersion(componentSourceCompatibility) + assumeSwiftCompilerSupportsLanguageVersion(xctestSourceCompatibility) makeSingleProject() fixture.writeToProject(testDirectory) buildFile << """ @@ -85,9 +90,15 @@ abstract class AbstractSwiftXCTestComponentWithTestedComponentIntegrationTest ex fixture.assertTestCasesRan(testExecutionResult) where: - fixture | componentSourceCompatibility | xctestSourceCompatibility + fixture | componentSourceCompatibility | xctestSourceCompatibility new Swift3WithSwift4XCTest('project') | SwiftVersion.SWIFT3 | SwiftVersion.SWIFT4 new Swift4WithSwift3XCTest('project') | SwiftVersion.SWIFT4 | SwiftVersion.SWIFT3 + new Swift5WithSwift4XCTest('project') | SwiftVersion.SWIFT5 | SwiftVersion.SWIFT4 + } + + void assumeSwiftCompilerSupportsLanguageVersion(SwiftVersion swiftVersion) { + assert toolChain != null, "You need to specify Swift tool chain requirement with 'requireSwiftToolChain()'" + assumeTrue((toolChain.version.major == 5 && swiftVersion.version in [5, 4]) || (toolChain.version.major == 4 && swiftVersion.version in [4, 3]) || (toolChain.version.major == 3 && swiftVersion.version == 3)) } abstract String getTestedComponentDsl() diff --git a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cpp/plugins/CppUnitTestPlugin.java b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cpp/plugins/CppUnitTestPlugin.java index 86ec3a9690231..683331ca6e3c4 100644 --- a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cpp/plugins/CppUnitTestPlugin.java +++ b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/cpp/plugins/CppUnitTestPlugin.java @@ -192,12 +192,12 @@ private void configureTestSuiteWithTestedComponentWhenAvailable(Project project, // Configure test binary to link against tested component compiled objects ConfigurableFileCollection testableObjects = project.files(); if (target instanceof CppApplication) { + // TODO - this should be an outgoing variant of the component under test TaskProvider unexportMainSymbol = tasks.register(testExecutable.getNames().getTaskName("relocateMainFor"), UnexportMainSymbol.class, task -> { - task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir("obj/main/for-test")); + String dirName = ((DefaultCppBinary) testedBinary).getNames().getDirName(); + task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir("obj/for-test/" + dirName)); task.getObjects().from(testedBinary.getObjects()); }); - // TODO: builtBy unnecessary? - testableObjects.builtBy(unexportMainSymbol); testableObjects.from(unexportMainSymbol.map(task -> task.getRelocatedObjects())); } else { testableObjects.from(testedBinary.getObjects()); diff --git a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/xctest/plugins/XCTestConventionPlugin.java b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/xctest/plugins/XCTestConventionPlugin.java index ef3e487a60a64..80cf51923e1fd 100644 --- a/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/xctest/plugins/XCTestConventionPlugin.java +++ b/subprojects/testing-native/src/main/java/org/gradle/nativeplatform/test/xctest/plugins/XCTestConventionPlugin.java @@ -261,11 +261,10 @@ private void configureTestSuiteWithTestedComponentWhenAvailable(final Project pr ConfigurableFileCollection testableObjects = project.files(); if (testedComponent instanceof SwiftApplication) { TaskProvider unexportMainSymbol = tasks.register("relocateMainForTest", UnexportMainSymbol.class, task -> { - task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir("obj/main/for-test")); + String dirName = ((DefaultSwiftBinary) testedBinary).getNames().getDirName(); + task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir("obj/for-test/" + dirName)); task.getObjects().from(testedBinary.getObjects()); }); - // TODO: builtBy unnecessary? - testableObjects.builtBy(unexportMainSymbol); testableObjects.from(unexportMainSymbol.map(task -> task.getRelocatedObjects())); } else { testableObjects.from(testedBinary.getObjects()); diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/ToolingApiUnsupportedBuildJvmCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/ToolingApiUnsupportedBuildJvmCrossVersionSpec.groovy index b35700581fd9d..ae83504c8f2ab 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/ToolingApiUnsupportedBuildJvmCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/ToolingApiUnsupportedBuildJvmCrossVersionSpec.groovy @@ -21,6 +21,7 @@ import org.gradle.integtests.fixtures.AvailableJavaHomes import org.gradle.integtests.tooling.fixture.TargetGradleVersion import org.gradle.integtests.tooling.fixture.ToolingApiSpecification import org.gradle.integtests.tooling.r18.BrokenAction +import org.gradle.tooling.GradleConnectionException import org.gradle.tooling.ProjectConnection import org.gradle.tooling.model.GradleProject import org.gradle.util.Requires @@ -42,9 +43,9 @@ class ToolingApiUnsupportedBuildJvmCrossVersionSpec extends ToolingApiSpecificat } then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.message.startsWith("Could not execute build using Gradle ") - caughtGradleConnectionException.cause.message == "Gradle ${targetDist.version.version} requires Java 8 or later to run. Your build is currently configured to use Java ${jdk.javaVersion.majorVersion}." + GradleConnectionException e = thrown() + e.message.startsWith("Could not execute build using Gradle ") + e.cause.message == "Gradle ${targetDist.version.version} requires Java 8 or later to run. Your build is currently configured to use Java ${jdk.javaVersion.majorVersion}." where: jdk << AvailableJavaHomes.getJdks("1.5", "1.6", "1.7") @@ -60,9 +61,9 @@ class ToolingApiUnsupportedBuildJvmCrossVersionSpec extends ToolingApiSpecificat } then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.message.startsWith("Could not fetch model of type 'GradleProject' using Gradle ") - caughtGradleConnectionException.cause.message == "Gradle ${targetDist.version.version} requires Java 8 or later to run. Your build is currently configured to use Java ${jdk.javaVersion.majorVersion}." + GradleConnectionException e = thrown() + e.message.startsWith("Could not fetch model of type 'GradleProject' using Gradle ") + e.cause.message == "Gradle ${targetDist.version.version} requires Java 8 or later to run. Your build is currently configured to use Java ${jdk.javaVersion.majorVersion}." where: jdk << AvailableJavaHomes.getJdks("1.5", "1.6", "1.7") @@ -78,9 +79,9 @@ class ToolingApiUnsupportedBuildJvmCrossVersionSpec extends ToolingApiSpecificat } then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.message.startsWith("Could not run build action using Gradle ") - caughtGradleConnectionException.cause.message == "Gradle ${targetDist.version.version} requires Java 8 or later to run. Your build is currently configured to use Java ${jdk.javaVersion.majorVersion}." + GradleConnectionException e = thrown() + e.message.startsWith("Could not run build action using Gradle ") + e.cause.message == "Gradle ${targetDist.version.version} requires Java 8 or later to run. Your build is currently configured to use Java ${jdk.javaVersion.majorVersion}." where: jdk << AvailableJavaHomes.getJdks("1.5", "1.6", "1.7") @@ -96,9 +97,9 @@ class ToolingApiUnsupportedBuildJvmCrossVersionSpec extends ToolingApiSpecificat } then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.message.startsWith("Could not execute tests using Gradle ") - caughtGradleConnectionException.cause.message == "Gradle ${targetDist.version.version} requires Java 8 or later to run. Your build is currently configured to use Java ${jdk.javaVersion.majorVersion}." + GradleConnectionException e = thrown() + e.message.startsWith("Could not execute tests using Gradle ") + e.cause.message == "Gradle ${targetDist.version.version} requires Java 8 or later to run. Your build is currently configured to use Java ${jdk.javaVersion.majorVersion}." where: jdk << AvailableJavaHomes.getJdks("1.5", "1.6", "1.7") diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/m9/DaemonErrorFeedbackCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/m9/DaemonErrorFeedbackCrossVersionSpec.groovy index 7eddc8528f066..2d94fa34fa8d8 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/m9/DaemonErrorFeedbackCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/m9/DaemonErrorFeedbackCrossVersionSpec.groovy @@ -17,6 +17,7 @@ package org.gradle.integtests.tooling.m9 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification +import org.gradle.tooling.GradleConnectionException import spock.lang.Issue import spock.lang.Timeout @@ -37,8 +38,8 @@ class DaemonErrorFeedbackCrossVersionSpec extends ToolingApiSpecification { } then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.cause.message.contains "-Xasdf" - caughtGradleConnectionException.cause.message.contains "Unable to start the daemon" + GradleConnectionException ex = thrown() + ex.cause.message.contains "-Xasdf" + ex.cause.message.contains "Unable to start the daemon" } } diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r21/TaskVisibilityCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r21/TaskVisibilityCrossVersionSpec.groovy index ac119e97bd452..a9db48696ca7b 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r21/TaskVisibilityCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r21/TaskVisibilityCrossVersionSpec.groovy @@ -54,8 +54,8 @@ project(':b:c') { } def "task visibility is correct"() { - def publicTasks = rootProjectImplicitTasks + ['t2'] - def publicSelectors = rootProjectImplicitSelectors + ['t1', 't2'] + def publicTasks = rootProjectImplicitTasks - implicitInvisibleTasks + ['t2'] + def publicSelectors = rootProjectImplicitSelectors - implicitInvisibleSelectors + ['t1', 't2'] when: BuildInvocations model = withConnection { connection -> diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/DeploymentHandleContinuousBuildCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/DeploymentHandleContinuousBuildCrossVersionSpec.groovy index 4746f9dfc6973..0134b2f442f79 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/DeploymentHandleContinuousBuildCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/DeploymentHandleContinuousBuildCrossVersionSpec.groovy @@ -19,118 +19,40 @@ package org.gradle.integtests.tooling.r25 import org.gradle.integtests.fixtures.executer.GradleVersions import org.gradle.integtests.tooling.fixture.ContinuousBuildToolingApiSpecification import org.gradle.integtests.tooling.fixture.TargetGradleVersion +import org.gradle.test.fixtures.TestDeploymentFixture import spock.lang.Timeout @Timeout(120) @TargetGradleVersion(GradleVersions.SUPPORTS_DEPLOYMENT_REGISTRY) class DeploymentHandleContinuousBuildCrossVersionSpec extends ContinuousBuildToolingApiSpecification { - File triggerFile = file('triggerFile') - File keyFile = file('keyFile') + def fixture = new TestDeploymentFixture() def setup() { - buildFile << """ - import javax.inject.Inject - import org.gradle.deployment.internal.Deployment - import org.gradle.deployment.internal.DeploymentHandle - import org.gradle.deployment.internal.DeploymentRegistry - import org.gradle.deployment.internal.DeploymentRegistry.ChangeBehavior - - task runDeployment(type: RunTestDeployment) { - triggerFile = file('${triggerFile.name}') - keyFile = file('${keyFile.name}') - } - - class TestDeploymentHandle implements DeploymentHandle { - final File keyFile - boolean running - - @Inject - TestDeploymentHandle(key, File keyFile) { - this.keyFile = keyFile - keyFile.text = key - } - - public void start(Deployment deployment) { - running = true - } - - public boolean isRunning() { - return running - } - - public void stop() { - running = false - keyFile.delete() - } - } - - class RunTestDeployment extends DefaultTask { - @InputFile - File triggerFile - - @OutputFile - File keyFile - - @Inject - protected DeploymentRegistry getDeploymentRegistry() { - throw new UnsupportedOperationException() - } - - @TaskAction - void doDeployment() { - // we set a different key for every build so we can detect if we - // somehow get a different deployment handle between builds - def key = System.currentTimeMillis() - - TestDeploymentHandle handle = getDeploymentRegistry().get('test', TestDeploymentHandle.class) - if (handle == null) { - // This should only happen once (1st build), so if we get a different value in keyFile between - // builds then we know we can detect if we didn't get the same handle - handle = getDeploymentRegistry().start('test', DeploymentRegistry.ChangeBehavior.NONE, TestDeploymentHandle.class, key, keyFile) - } - - println "\\nCurrent Key: \$key, Deployed Key: \$handle.keyFile.text" - assert handle.isRunning() - } - } - """ + fixture.writeToProject(projectDir) buildTimeout = 30 } def "deployment is stopped when continuous build is cancelled" () { - triggerFile.text = "0" - when: runBuild(["runDeployment"]) { succeeds() - def key = file('keyFile').text - deploymentIsRunning(key) + def key = fixture.keyFile.text + fixture.assertDeploymentIsRunning(key) - waitBeforeModification triggerFile - triggerFile << "\n#a change" + waitBeforeModification fixture.triggerFile + fixture.triggerFile << "\n#a change" succeeds() - deploymentIsRunning(key) + fixture.assertDeploymentIsRunning(key) - waitBeforeModification triggerFile - triggerFile << "\n#another change" + waitBeforeModification fixture.triggerFile + fixture.triggerFile << "\n#another change" succeeds() - deploymentIsRunning(key) + fixture.assertDeploymentIsRunning(key) cancel() } then: - deploymentIsStopped() - } - - void deploymentIsRunning(String key) { - // assert that the keyFile still exists and has the same contents (ie handle is still running) - assert keyFile.exists() - assert keyFile.text == key - } - - void deploymentIsStopped() { - // assert that the deployment handle was stopped and removed the key file - assert !keyFile.exists() + fixture.assertDeploymentIsStopped() } } diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/TestProgressCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/TestProgressCrossVersionSpec.groovy index 42a5f735e5d93..6e9204b631038 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/TestProgressCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/TestProgressCrossVersionSpec.groovy @@ -470,7 +470,7 @@ class TestProgressCrossVersionSpec extends ToolingApiSpecification { def goodCode() { buildFile << """ apply plugin: 'java' - sourceCompatibility = 1.6 + sourceCompatibility = 1.7 ${mavenCentralRepository()} dependencies { testCompile 'junit:junit:4.12' } compileTestJava.options.fork = true // forked as 'Gradle Test Executor 1' diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/TestProgressDaemonErrorsCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/TestProgressDaemonErrorsCrossVersionSpec.groovy index 7c88d893ec0a6..72e99f6bccc7c 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/TestProgressDaemonErrorsCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r25/TestProgressDaemonErrorsCrossVersionSpec.groovy @@ -18,6 +18,7 @@ package org.gradle.integtests.tooling.r25 import org.gradle.integtests.tooling.fixture.ToolingApiSpecification import org.gradle.test.fixtures.server.http.BlockingHttpServer +import org.gradle.tooling.GradleConnectionException import org.gradle.tooling.ProjectConnection import org.gradle.tooling.events.OperationType import org.gradle.tooling.events.ProgressEvent @@ -51,8 +52,8 @@ class TestProgressDaemonErrorsCrossVersionSpec extends ToolingApiSpecification { } then: "build fails with a DaemonDisappearedException" - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.cause.message.contains('Gradle build daemon disappeared unexpectedly') + GradleConnectionException ex = thrown() + ex.cause.message.contains('Gradle build daemon disappeared unexpectedly') and: !result.empty diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r32/NonSerializableExceptionCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r32/NonSerializableExceptionCrossVersionSpec.groovy index 8505095b50a63..10d60ed6b7b8f 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r32/NonSerializableExceptionCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r32/NonSerializableExceptionCrossVersionSpec.groovy @@ -159,8 +159,7 @@ task run { } - private String getStackTraceAsString(GradleConnectionException throwable) { - caughtGradleConnectionException = throwable + private static String getStackTraceAsString(GradleConnectionException throwable) { StringWriter stringWriter = new StringWriter() throwable.printStackTrace(new PrintWriter(stringWriter)) return stringWriter.toString() diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r40/ProjectConfigurationChildrenProgressCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r40/ProjectConfigurationChildrenProgressCrossVersionSpec.groovy index 60aa881adf626..5749439d53543 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r40/ProjectConfigurationChildrenProgressCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r40/ProjectConfigurationChildrenProgressCrossVersionSpec.groovy @@ -116,7 +116,9 @@ class ProjectConfigurationChildrenProgressCrossVersionSpec extends ToolingApiSpe events.assertIsABuild() and: - events.operation('Configure project :').children.size() <= 5 //only 'Apply plugin org.gradle.help-tasks', maybe before/afterEvaluated and 2 delayed task registrations + events.operation('Configure project :').descendants { + it.descriptor.displayName.contains("Apply script") + }.isEmpty() } def "generates events for applied build scripts"() { diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r43/ToolingApiUnsupportedConsumerCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r43/ToolingApiUnsupportedConsumerCrossVersionSpec.groovy index 6808222ce4c33..1a14f94aec9f9 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r43/ToolingApiUnsupportedConsumerCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r43/ToolingApiUnsupportedConsumerCrossVersionSpec.groovy @@ -17,6 +17,7 @@ package org.gradle.integtests.tooling.r43 import org.gradle.integtests.tooling.fixture.ToolingApiVersion +import org.gradle.tooling.GradleConnectionException import org.gradle.util.GradleVersion @ToolingApiVersion("current") @@ -27,8 +28,8 @@ class ToolingApiUnsupportedConsumerCrossVersionSpec extends ToolingApiVersionSpe build() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.cause.message.contains('Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You should upgrade your tooling API client to version 3.0 or later.') + GradleConnectionException connectionException = thrown() + connectionException.cause.message.contains('Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You should upgrade your tooling API client to version 3.0 or later.') } @ToolingApiVersion(">=1.2 <3.0") @@ -37,8 +38,8 @@ class ToolingApiUnsupportedConsumerCrossVersionSpec extends ToolingApiVersionSpe build() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.cause.message.contains("Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You are currently using tooling API version ${GradleVersion.current().version}. You should upgrade your tooling API client to version 3.0 or later.") + GradleConnectionException connectionException = thrown() + connectionException.cause.message.contains("Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You are currently using tooling API version ${GradleVersion.current().version}. You should upgrade your tooling API client to version 3.0 or later.") } @ToolingApiVersion("<1.2") @@ -47,8 +48,8 @@ class ToolingApiUnsupportedConsumerCrossVersionSpec extends ToolingApiVersionSpe getModel() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.cause.message.contains('Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You should upgrade your tooling API client to version 3.0 or later.') + GradleConnectionException connectionException = thrown() + connectionException.cause.message.contains('Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You should upgrade your tooling API client to version 3.0 or later.') } @ToolingApiVersion(">=1.2 <3.0") @@ -57,8 +58,8 @@ class ToolingApiUnsupportedConsumerCrossVersionSpec extends ToolingApiVersionSpe getModel() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.cause.message.contains("Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You are currently using tooling API version ${GradleVersion.current().version}. You should upgrade your tooling API client to version 3.0 or later.") + GradleConnectionException connectionException = thrown() + connectionException.cause.message.contains("Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You are currently using tooling API version ${GradleVersion.current().version}. You should upgrade your tooling API client to version 3.0 or later.") } @ToolingApiVersion(">=1.8 <3.0") @@ -67,8 +68,8 @@ class ToolingApiUnsupportedConsumerCrossVersionSpec extends ToolingApiVersionSpe buildAction() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.cause.message.contains("Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You are currently using tooling API version ${GradleVersion.current().version}. You should upgrade your tooling API client to version 3.0 or later.") + GradleConnectionException connectionException = thrown() + connectionException.cause.message.contains("Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You are currently using tooling API version ${GradleVersion.current().version}. You should upgrade your tooling API client to version 3.0 or later.") } @ToolingApiVersion(">=2.6 <3.0") @@ -77,7 +78,7 @@ class ToolingApiUnsupportedConsumerCrossVersionSpec extends ToolingApiVersionSpe testExecution() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.cause.message.contains("Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You are currently using tooling API version ${GradleVersion.current().version}. You should upgrade your tooling API client to version 3.0 or later.") + GradleConnectionException connectionException = thrown() + connectionException.cause.message.contains("Support for clients using a tooling API version older than 3.0 was removed in Gradle 5.0. You are currently using tooling API version ${GradleVersion.current().version}. You should upgrade your tooling API client to version 3.0 or later.") } } diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r43/ToolingApiUnsupportedProviderCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r43/ToolingApiUnsupportedProviderCrossVersionSpec.groovy index 4153a2a132540..b9a67e5a12868 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r43/ToolingApiUnsupportedProviderCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r43/ToolingApiUnsupportedProviderCrossVersionSpec.groovy @@ -18,6 +18,7 @@ package org.gradle.integtests.tooling.r43 import org.gradle.integtests.tooling.fixture.TargetGradleVersion import org.gradle.integtests.tooling.fixture.ToolingApiVersion +import org.gradle.tooling.GradleConnectionException @ToolingApiVersion("current") class ToolingApiUnsupportedProviderCrossVersionSpec extends ToolingApiVersionSpecification { @@ -27,8 +28,8 @@ class ToolingApiUnsupportedProviderCrossVersionSpec extends ToolingApiVersionSpe build() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.message.contains("Support for builds using Gradle versions older than 2.6 was removed in tooling API version 5.0. You are currently using Gradle version ${targetDist.version.version}. You should upgrade your Gradle build to use Gradle 2.6 or later.") + GradleConnectionException connectionException = thrown() + connectionException.message.contains("Support for builds using Gradle versions older than 2.6 was removed in tooling API version 5.0. You are currently using Gradle version ${targetDist.version.version}. You should upgrade your Gradle build to use Gradle 2.6 or later.") } @TargetGradleVersion("<2.6") @@ -37,8 +38,8 @@ class ToolingApiUnsupportedProviderCrossVersionSpec extends ToolingApiVersionSpe getModel() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.message.contains("Support for builds using Gradle versions older than 2.6 was removed in tooling API version 5.0. You are currently using Gradle version ${targetDist.version.version}. You should upgrade your Gradle build to use Gradle 2.6 or later.") + GradleConnectionException connectionException = thrown() + connectionException.message.contains("Support for builds using Gradle versions older than 2.6 was removed in tooling API version 5.0. You are currently using Gradle version ${targetDist.version.version}. You should upgrade your Gradle build to use Gradle 2.6 or later.") } @TargetGradleVersion("<2.6") @@ -47,8 +48,8 @@ class ToolingApiUnsupportedProviderCrossVersionSpec extends ToolingApiVersionSpe buildAction() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.message.contains("Support for builds using Gradle versions older than 2.6 was removed in tooling API version 5.0. You are currently using Gradle version ${targetDist.version.version}. You should upgrade your Gradle build to use Gradle 2.6 or later.") + GradleConnectionException connectionException = thrown() + connectionException.message.contains("Support for builds using Gradle versions older than 2.6 was removed in tooling API version 5.0. You are currently using Gradle version ${targetDist.version.version}. You should upgrade your Gradle build to use Gradle 2.6 or later.") } @TargetGradleVersion("<2.6") @@ -57,7 +58,7 @@ class ToolingApiUnsupportedProviderCrossVersionSpec extends ToolingApiVersionSpe testExecution() then: - caughtGradleConnectionException = thrown() - caughtGradleConnectionException.message.contains("Support for builds using Gradle versions older than 2.6 was removed in tooling API version 5.0. You are currently using Gradle version ${targetDist.version.version}. You should upgrade your Gradle build to use Gradle 2.6 or later.") + GradleConnectionException connectionException = thrown() + connectionException.message.contains("Support for builds using Gradle versions older than 2.6 was removed in tooling API version 5.0. You are currently using Gradle version ${targetDist.version.version}. You should upgrade your Gradle build to use Gradle 2.6 or later.") } } diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r51/ProjectConfigurationProgressEventCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r51/ProjectConfigurationProgressEventCrossVersionSpec.groovy index cf6b8519f3571..bbe618f6da037 100644 --- a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r51/ProjectConfigurationProgressEventCrossVersionSpec.groovy +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r51/ProjectConfigurationProgressEventCrossVersionSpec.groovy @@ -217,6 +217,7 @@ class ProjectConfigurationProgressEventCrossVersionSpec extends ToolingApiSpecif def "reports plugin configuration results for remote script plugins"() { given: + toolingApi.requireIsolatedUserHome() // So that the script is not cached server.start() def scriptUri = server.uri("script.gradle") server.expect(server.get("script.gradle").send(""" diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/IntermediateResultHandlerCollector.java b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/IntermediateResultHandlerCollector.java new file mode 100644 index 0000000000000..caef0103cc24e --- /dev/null +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/IntermediateResultHandlerCollector.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.tooling.r54; + +import org.gradle.tooling.IntermediateResultHandler; + +public class IntermediateResultHandlerCollector implements IntermediateResultHandler { + private T result = null; + + @Override + public void onComplete(T result) { + this.result = result; + } + + public T getResult() { + return result; + } +} diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/LoadEclipseModel.java b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/LoadEclipseModel.java new file mode 100644 index 0000000000000..2a61f09cef353 --- /dev/null +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/LoadEclipseModel.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.tooling.r54; + +import org.gradle.tooling.BuildAction; +import org.gradle.tooling.BuildController; +import org.gradle.tooling.model.eclipse.EclipseProject; + +import java.io.Serializable; + +public class LoadEclipseModel implements BuildAction, Serializable { + + @Override + public EclipseProject execute(BuildController controller) { + return controller.getModel(EclipseProject.class); + } +} diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/RunEclipseAutoBuildTasksCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/RunEclipseAutoBuildTasksCrossVersionSpec.groovy new file mode 100644 index 0000000000000..5f5219f5a7262 --- /dev/null +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/RunEclipseAutoBuildTasksCrossVersionSpec.groovy @@ -0,0 +1,188 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.tooling.r54 + + +import org.gradle.integtests.tooling.fixture.TargetGradleVersion +import org.gradle.integtests.tooling.fixture.ToolingApiSpecification +import org.gradle.integtests.tooling.fixture.ToolingApiVersion + +@TargetGradleVersion(">=5.4") +@ToolingApiVersion(">=5.4") +class RunEclipseAutoBuildTasksCrossVersionSpec extends ToolingApiSpecification { + def setup() { + file("sub1").mkdirs() + + buildFile << """ + apply plugin: 'eclipse' + + task foo { + } + + task bar { + } + + project(":sub") { + apply plugin: 'eclipse' + + task bar { + } + } + """ + settingsFile << "include 'sub'" + } + + def "can run tasks upon Eclipse auto-build"() { + setup: + buildFile << "eclipse { autoBuildTasks 'foo' }" + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunAutoBuildTasks(), projectsLoadedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + taskExecuted(out, ":foo") + } + + def "can use task reference in sync task list"() { + setup: + buildFile << "eclipse { autoBuildTasks foo }" + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunAutoBuildTasks(), projectsLoadedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + taskExecuted(out, ":foo") + } + + def "task paths are resolved correctly"() { + setup: + buildFile << """ + project(':sub') { + eclipse { + autoBuildTasks 'bar' + } + } + """ + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunAutoBuildTasks(), projectsLoadedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + taskExecuted(out, ":sub:bar") + } + + + def "execute placeholder task when no task is configured for auto-build"() { + setup: + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunAutoBuildTasks(), projectsLoadedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + taskExecuted(out, ":nothing") + } + + def "does not override client-specified tasks"() { + setup: + buildFile << "eclipse { autoBuildTasks bar }" + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunAutoBuildTasks(), projectsLoadedHandler) + .build() + .setStandardOutput(out) + .forTasks("foo") + .run() + } + + then: + taskExecuted(out, ":foo") + taskExecuted(out, ":bar") + } + + + def "placeholder task never overlaps with project task"() { + setup: + buildFile << """ + task nothing { + } + + task nothing_ { + } + """ + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunAutoBuildTasks(), projectsLoadedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + !taskExecuted(out, ":nothing") + !taskExecuted(out, ":nothing_") + taskExecuted(out, ":nothing__") + } + + private def taskExecuted(ByteArrayOutputStream out, String taskPath) { + out.toString().contains("> Task $taskPath ") + } + +} diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/RunEclipseSynchronizationTasksCrossVersionSpec.groovy b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/RunEclipseSynchronizationTasksCrossVersionSpec.groovy new file mode 100644 index 0000000000000..9d33ba3045fe6 --- /dev/null +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/RunEclipseSynchronizationTasksCrossVersionSpec.groovy @@ -0,0 +1,201 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.tooling.r54 + + +import org.gradle.integtests.tooling.fixture.TargetGradleVersion +import org.gradle.integtests.tooling.fixture.ToolingApiSpecification +import org.gradle.integtests.tooling.fixture.ToolingApiVersion +import org.gradle.plugins.ide.eclipse.model.EclipseModel + +@TargetGradleVersion(">=5.4") +@ToolingApiVersion(">=5.4") +class RunEclipseSynchronizationTasksCrossVersionSpec extends ToolingApiSpecification { + def setup() { + file("sub1").mkdirs() + + buildFile << """ + apply plugin: 'eclipse' + + task foo { + } + + task bar { + } + + project(":sub") { + apply plugin: 'eclipse' + + task bar { + } + } + """ + settingsFile << "include 'sub'" + } + + def "can run tasks upon Eclipse synchronization"() { + setup: + buildFile << "eclipse { synchronizationTasks 'foo' }" + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def buildFinishedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunSyncTasks(), projectsLoadedHandler) + .buildFinished(new LoadEclipseModel(), buildFinishedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + taskExecuted(out, ":foo") + } + + def "can use task reference in sync task list"() { + setup: + buildFile << "eclipse { synchronizationTasks foo }" + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def buildFinishedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunSyncTasks(), projectsLoadedHandler) + .buildFinished(new LoadEclipseModel(), buildFinishedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + taskExecuted(out, ":foo") + } + + def "task paths are resolved correctly"() { + setup: + buildFile << """ + project(':sub') { + eclipse { + synchronizationTasks 'bar' + } + } + """ + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def buildFinishedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunSyncTasks(), projectsLoadedHandler) + .buildFinished(new LoadEclipseModel(), buildFinishedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + taskExecuted(out, ":sub:bar") + } + + + def "execute placeholder task when no task is configured for synchronization"() { + setup: + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def buildFinishedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunSyncTasks(), projectsLoadedHandler) + .buildFinished(new LoadEclipseModel(), buildFinishedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + taskExecuted(out, ":nothing") + } + + def "does not override client-specified tasks"() { + setup: + buildFile << "eclipse { synchronizationTasks bar }" + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def buildFinishedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunSyncTasks(), projectsLoadedHandler) + .buildFinished(new LoadEclipseModel(), buildFinishedHandler) + .build() + .setStandardOutput(out) + .forTasks("foo") + .run() + } + + then: + taskExecuted(out, ":foo") + taskExecuted(out, ":bar") + } + + + def "placeholder task never overlaps with project task"() { + setup: + buildFile << """ + task nothing { + } + + task nothing_ { + } + """ + + def projectsLoadedHandler = new IntermediateResultHandlerCollector() + def buildFinishedHandler = new IntermediateResultHandlerCollector() + def out = new ByteArrayOutputStream() + + when: + withConnection { connection -> + connection.action().projectsLoaded(new TellGradleToRunSyncTasks(), projectsLoadedHandler) + .buildFinished(new LoadEclipseModel(), buildFinishedHandler) + .build() + .setStandardOutput(out) + .forTasks() + .run() + } + + then: + !taskExecuted(out, ":nothing") + !taskExecuted(out, ":nothing_") + taskExecuted(out, ":nothing__") + } + + private def taskExecuted(ByteArrayOutputStream out, String taskPath) { + out.toString().contains("> Task $taskPath ") + } + +} diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/TellGradleToRunAutoBuildTasks.java b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/TellGradleToRunAutoBuildTasks.java new file mode 100644 index 0000000000000..9d1ac85b2d4eb --- /dev/null +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/TellGradleToRunAutoBuildTasks.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.tooling.r54; + +import org.gradle.tooling.BuildAction; +import org.gradle.tooling.BuildController; +import org.gradle.tooling.model.eclipse.RunEclipseAutoBuildTasks; + +import java.io.Serializable; + +public class TellGradleToRunAutoBuildTasks implements BuildAction, Serializable { + + @Override + public Void execute(BuildController controller) { + controller.getModel(RunEclipseAutoBuildTasks.class); + return null; + } +} diff --git a/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/TellGradleToRunSyncTasks.java b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/TellGradleToRunSyncTasks.java new file mode 100644 index 0000000000000..a79894ce3becf --- /dev/null +++ b/subprojects/tooling-api/src/crossVersionTest/groovy/org/gradle/integtests/tooling/r54/TellGradleToRunSyncTasks.java @@ -0,0 +1,32 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.tooling.r54; + +import org.gradle.tooling.BuildAction; +import org.gradle.tooling.BuildController; +import org.gradle.tooling.model.eclipse.RunEclipseSynchronizationTasks; + +import java.io.Serializable; + +public class TellGradleToRunSyncTasks implements BuildAction, Serializable { + + @Override + public Void execute(BuildController controller) { + controller.getModel(RunEclipseSynchronizationTasks.class); + return null; + } +} diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/TypeInspector.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/TypeInspector.java index 4a64757a2786c..886a30ed014b9 100644 --- a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/TypeInspector.java +++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/adapter/TypeInspector.java @@ -16,7 +16,7 @@ package org.gradle.tooling.internal.adapter; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Method; diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ParametrizedActionRunner.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ParameterizedActionRunner.java similarity index 100% rename from subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ParametrizedActionRunner.java rename to subprojects/tooling-api/src/main/java/org/gradle/tooling/internal/consumer/connection/ParameterizedActionRunner.java diff --git a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/ParameterizedTransformSpec.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/RunEclipseAutoBuildTasks.java similarity index 59% rename from subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/ParameterizedTransformSpec.java rename to subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/RunEclipseAutoBuildTasks.java index 7635456c66431..3dd28ee18d1e9 100644 --- a/subprojects/core-api/src/main/java/org/gradle/api/artifacts/transform/ParameterizedTransformSpec.java +++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/RunEclipseAutoBuildTasks.java @@ -14,26 +14,19 @@ * limitations under the License. */ -package org.gradle.api.artifacts.transform; +package org.gradle.tooling.model.eclipse; -import org.gradle.api.Action; import org.gradle.api.Incubating; /** - * Defines an artifact transformation. + * A tooling model that instructs Gradle to run tasks from the Eclipse plugin configuration. * - * @param The transform specific parameter type. - * @since 5.3 + * Similarly to {@link RunEclipseSynchronizationTasks}, this is a special tooling model as it does + * not provide any information. However, when requested, Gradle will override the client-provided + * tasks with the ones stored in the {@code eclipse.autoBuildTasks} property. + * + * @since 5.4 */ @Incubating -public interface ParameterizedTransformSpec extends TransformSpec { - /** - * The parameters for the transform action. - */ - T getParameters(); - - /** - * Configure the parameters for the transform action. - */ - void parameters(Action action); +public interface RunEclipseAutoBuildTasks { } diff --git a/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/RunEclipseSynchronizationTasks.java b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/RunEclipseSynchronizationTasks.java new file mode 100644 index 0000000000000..20f57bae8ed94 --- /dev/null +++ b/subprojects/tooling-api/src/main/java/org/gradle/tooling/model/eclipse/RunEclipseSynchronizationTasks.java @@ -0,0 +1,33 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.tooling.model.eclipse; + +import org.gradle.api.Incubating; + +/** + * A tooling model that instructs Gradle to run tasks from the Eclipse plugin configuration. + * + * This is a special tooling model as it does not provide any information. However, when requested, Gradle will + * override the client-provided tasks with the ones stored in the {@code eclipse.synchronizationTasks} property. + * + * This allows Buildship to run tasks before the model loading and load the models in a single step. + * + * @since 5.4 + */ +@Incubating +public interface RunEclipseSynchronizationTasks { +} diff --git a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy index e37af9f9d6c5a..d26c350671c99 100644 --- a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy +++ b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApi.groovy @@ -182,7 +182,7 @@ class ToolingApi implements TestRule { if (gradleUserHomeDir != context.gradleUserHomeDir) { // When using an isolated user home, first initialise the Gradle instance using the default user home dir - // This sets some some static state that uses files from the use home dir, such as DLLs + // This sets some static state that uses files from the user home dir, such as DLLs connector.useGradleUserHomeDir(new File(context.gradleUserHomeDir.path)) def connection = connector.connect() try { diff --git a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiAdditionalClasspath.groovy b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiAdditionalClasspath.groovy new file mode 100644 index 0000000000000..9c4ee083b405b --- /dev/null +++ b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiAdditionalClasspath.groovy @@ -0,0 +1,36 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.tooling.fixture + +import java.lang.annotation.ElementType +import java.lang.annotation.Inherited +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy +import java.lang.annotation.Target + + +/** + * Declares the provider to use for TAPI client additional classpath. + * + * @see ToolingApiAdditionalClasspathProvider + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +@interface ToolingApiAdditionalClasspath { + Class value() +} diff --git a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiAdditionalClasspathProvider.groovy b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiAdditionalClasspathProvider.groovy new file mode 100644 index 0000000000000..18e7291596289 --- /dev/null +++ b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiAdditionalClasspathProvider.groovy @@ -0,0 +1,31 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.gradle.integtests.tooling.fixture + +import org.gradle.integtests.fixtures.executer.GradleDistribution + + +/** + * Provides TAPI client additional classpath. + */ +interface ToolingApiAdditionalClasspathProvider { + + /** + * Additional classpath for given target Gradle distribution to be added to the loader of the test class. + */ + List additionalClasspathFor(GradleDistribution distribution) +} diff --git a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy index 708dda00838e3..8de4a895016a1 100755 --- a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy +++ b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiCompatibilitySuiteRunner.groovy @@ -21,7 +21,15 @@ import org.gradle.integtests.fixtures.executer.GradleDistribution import org.gradle.integtests.fixtures.versions.ReleasedVersionDistributions class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner { - static ToolingApiDistributionResolver resolver + + private static ToolingApiDistributionResolver resolver + + private static ToolingApiDistributionResolver getResolver() { + if (resolver == null) { + resolver = new ToolingApiDistributionResolver().withDefaultRepository() + } + return resolver + } ToolingApiCompatibilitySuiteRunner(Class target) { super(target) @@ -35,15 +43,9 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner return previousVersions.all } - private static ToolingApiDistributionResolver getResolver() { - if (resolver == null) { - resolver = new ToolingApiDistributionResolver().withDefaultRepository() - } - return resolver - } - @Override protected void createExecutionsForContext(CoverageContext coverageContext) { + // current vs. current add(new ToolingApiExecution(getResolver().resolve(current.version.version), current)) super.createExecutionsForContext(coverageContext) } @@ -54,7 +56,9 @@ class ToolingApiCompatibilitySuiteRunner extends AbstractCompatibilityTestRunner def distribution = versionedTool.distribution if (distribution.toolingApiSupported) { + // current vs. target executions.add(new ToolingApiExecution(getResolver().resolve(current.version.version), distribution)) + // target vs. current executions.add(new ToolingApiExecution(getResolver().resolve(distribution.version.version), current)) } diff --git a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiExecution.groovy b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiExecution.groovy index 45779375fff74..fcc462307392e 100644 --- a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiExecution.groovy +++ b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiExecution.groovy @@ -58,12 +58,12 @@ class ToolingApiExecution extends AbstractMultiTestRunner.Execution implements T // Trying to use @Unroll with a tooling api runner causes NPEs in the test fixtures // Fail early with a message until we can fix this properly. Unroll unroll = testDetails.getAnnotation(Unroll) - if (unroll!=null) { + if (unroll != null) { throw new IllegalArgumentException("Cannot use @Unroll with Tooling Api tests") } if (!gradle.daemonIdleTimeoutConfigurable && OperatingSystem.current().isWindows()) { - //Older daemon don't have configurable ttl and they hung for 3 hours afterwards. + // Older daemon don't have configurable ttl and they hung for 3 hours afterwards. // This is a real problem on windows due to eager file locking and continuous CI failures. // On linux it's a lesser problem - long-lived daemons hung and steal resources but don't lock files. // So, for windows we'll only run tests against target gradle that supports ttl @@ -99,11 +99,19 @@ class ToolingApiExecution extends AbstractMultiTestRunner.Execution implements T testClassPath << ClasspathUtil.getClasspathForClass(target) testClassPath << ClasspathUtil.getClasspathForClass(TestResultHandler) + testClassPath.addAll(collectAdditionalClasspath()) + getTestClassLoader(TEST_CLASS_LOADERS, toolingApi, testClassPath) { it.allowResources(target.name.replace('.', '/')) } } + private List collectAdditionalClasspath() { + target.annotations.findAll { it instanceof ToolingApiAdditionalClasspath }.collectMany { annotation -> + (annotation as ToolingApiAdditionalClasspath).value().newInstance().additionalClasspathFor(gradle) + } + } + @Override protected void before() { def testClazz = testClassLoader.loadClass(ToolingApiSpecification.name) diff --git a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy index 0ba705f59cc17..20be71327c94e 100644 --- a/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy +++ b/subprojects/tooling-api/src/testFixtures/groovy/org/gradle/integtests/tooling/fixture/ToolingApiSpecification.groovy @@ -44,6 +44,7 @@ import spock.lang.Specification import static org.gradle.integtests.fixtures.RetryConditions.onIssueWithReleasedGradleVersion import static spock.lang.Retry.Mode.SETUP_FEATURE_CLEANUP + /** * A spec that executes tests against all compatible versions of tooling API consumer and testDirectoryProvider, including the current Gradle version under test. * @@ -84,6 +85,7 @@ abstract class ToolingApiSpecification extends Specification { @Rule public RuleChain chain = RuleChain.outerRule(temporaryFolder).around(temporaryDistributionFolder).around(toolingApi) + // reflectively invoked by ToolingApiCompatibilitySuiteRunner static void selectTargetDist(GradleDistribution version) { VERSION.set(version) } @@ -137,14 +139,24 @@ abstract class ToolingApiSpecification extends Specification { } public void withConnector(@DelegatesTo(GradleConnector) @ClosureParams(value = SimpleType, options = ["org.gradle.tooling.GradleConnector"]) Closure cl) { - toolingApi.withConnector(cl) + try { + toolingApi.withConnector(cl) + } catch (GradleConnectionException e) { + caughtGradleConnectionException = e + throw e + } } public T withConnection(GradleConnector connector, @DelegatesTo(ProjectConnection) @ClosureParams(value = SimpleType, options = ["org.gradle.tooling.ProjectConnection"]) Closure cl) { - toolingApi.withConnection(connector, cl) + try { + return toolingApi.withConnection(connector, cl) + } catch (GradleConnectionException e) { + caughtGradleConnectionException = e + throw e + } } - def connector() { + GradleConnector connector() { toolingApi.connector() } @@ -176,12 +188,14 @@ abstract class ToolingApiSpecification extends Specification { } /** - * Returns the set of implicit task names expected for a non-root project for the target Gradle version. + * Returns the set of implicit task names expected for any project for the target Gradle version. */ Set getImplicitTasks() { - if (targetVersion > GradleVersion.version("3.1")) { + if (targetVersion >= GradleVersion.version("5.3")) { + return ['buildEnvironment', 'components', 'dependencies', 'dependencyInsight', 'dependentComponents', 'help', 'projects', 'properties', 'tasks', 'model', 'prepareKotlinBuildScriptModel'] + } else if (targetVersion > GradleVersion.version("3.1")) { return ['buildEnvironment', 'components', 'dependencies', 'dependencyInsight', 'dependentComponents', 'help', 'projects', 'properties', 'tasks', 'model'] - } else if (GradleVersion.version(targetDist.version.baseVersion.version) >= GradleVersion.version("2.10")) { + } else if (targetVersion >= GradleVersion.version("2.10")) { return ['buildEnvironment', 'components', 'dependencies', 'dependencyInsight', 'help', 'projects', 'properties', 'tasks', 'model'] } else { return ['components', 'dependencies', 'dependencyInsight', 'help', 'projects', 'properties', 'tasks', 'model'] @@ -189,7 +203,7 @@ abstract class ToolingApiSpecification extends Specification { } /** - * Returns the set of implicit selector names expected for a non-root project for the target Gradle version. + * Returns the set of implicit selector names expected for any project for the target Gradle version. * *

    Note that in some versions the handling of implicit selectors was broken, so this method may return a different value * to {@link #getImplicitTasks()}. @@ -198,6 +212,22 @@ abstract class ToolingApiSpecification extends Specification { return getImplicitTasks() } + /** + * Returns the set of invisible implicit task names expected for any project for the target Gradle version. + */ + Set getImplicitInvisibleTasks() { + return targetVersion >= GradleVersion.version("5.3") ? ['prepareKotlinBuildScriptModel'] : [] + } + + /** + * Returns the set of invisible implicit selector names expected for any project for the target Gradle version. + * + * See {@link #getImplicitInvisibleTasks}. + */ + Set getImplicitInvisibleSelectors() { + return implicitInvisibleTasks + } + /** * Returns the set of implicit task names expected for a root project for the target Gradle version. */ diff --git a/subprojects/tooling-api/tooling-api.gradle.kts b/subprojects/tooling-api/tooling-api.gradle.kts index 92ed24e9ba7cf..860aea0a6f110 100644 --- a/subprojects/tooling-api/tooling-api.gradle.kts +++ b/subprojects/tooling-api/tooling-api.gradle.kts @@ -46,7 +46,6 @@ dependencies { compile(project(":wrapper")) compile(project(":baseServices")) publishCompile(library("slf4j_api")) { version { prefer(libraryVersion("slf4j_api")) } } - compile(library("jcip")) testFixturesApi(project(":baseServicesGroovy")) testFixturesApi(project(":internalIntegTesting")) diff --git a/subprojects/version-control/src/main/java/org/gradle/vcs/internal/resolver/OfflineVcsVersionWorkingDirResolver.java b/subprojects/version-control/src/main/java/org/gradle/vcs/internal/resolver/OfflineVcsVersionWorkingDirResolver.java index 00df1d8e2618b..b9c8842591d1f 100644 --- a/subprojects/version-control/src/main/java/org/gradle/vcs/internal/resolver/OfflineVcsVersionWorkingDirResolver.java +++ b/subprojects/version-control/src/main/java/org/gradle/vcs/internal/resolver/OfflineVcsVersionWorkingDirResolver.java @@ -36,7 +36,7 @@ public OfflineVcsVersionWorkingDirResolver(PersistentVcsMetadataCache persistent public File selectVersion(ModuleComponentSelector selector, VersionControlRepositoryConnection repository) { VersionRef previousVersion = persistentCache.getVersionForSelector(repository, selector.getVersionConstraint()); if (previousVersion == null) { - throw new ModuleVersionResolveException(selector, String.format("Cannot resolve %s from %s in offline mode.", selector.getDisplayName(), repository.getDisplayName())); + throw new ModuleVersionResolveException(selector, () -> String.format("Cannot resolve %s from %s in offline mode.", selector.getDisplayName(), repository.getDisplayName())); } // Reuse the same version as last build diff --git a/subprojects/version-control/src/main/java/org/gradle/vcs/internal/resolver/VcsDependencyResolver.java b/subprojects/version-control/src/main/java/org/gradle/vcs/internal/resolver/VcsDependencyResolver.java index a9ec2b8e9ddd9..b1c8f446b86eb 100644 --- a/subprojects/version-control/src/main/java/org/gradle/vcs/internal/resolver/VcsDependencyResolver.java +++ b/subprojects/version-control/src/main/java/org/gradle/vcs/internal/resolver/VcsDependencyResolver.java @@ -119,7 +119,7 @@ public boolean isSatisfiedBy(Pair spec.getDisplayName() + " did not contain a project publishing the specified dependency.")); } else { LocalComponentMetadata componentMetaData = localComponentRegistry.getComponent(entry.right); result.resolved(componentMetaData); diff --git a/subprojects/workers/src/main/java/org/gradle/workers/internal/WorkerDaemonFactory.java b/subprojects/workers/src/main/java/org/gradle/workers/internal/WorkerDaemonFactory.java index 2d97f3945b3a1..5a4c440aa4c7b 100644 --- a/subprojects/workers/src/main/java/org/gradle/workers/internal/WorkerDaemonFactory.java +++ b/subprojects/workers/src/main/java/org/gradle/workers/internal/WorkerDaemonFactory.java @@ -16,7 +16,7 @@ package org.gradle.workers.internal; -import net.jcip.annotations.ThreadSafe; +import javax.annotation.concurrent.ThreadSafe; import org.gradle.internal.operations.BuildOperationExecutor; import org.gradle.internal.operations.BuildOperationRef; import org.gradle.workers.IsolationMode; diff --git a/subprojects/workers/src/test/groovy/org/gradle/process/internal/worker/child/BootstrapSecurityManagerTest.groovy b/subprojects/workers/src/test/groovy/org/gradle/process/internal/worker/child/BootstrapSecurityManagerTest.groovy index 734e0bc9c2a21..76af46bd8c02e 100644 --- a/subprojects/workers/src/test/groovy/org/gradle/process/internal/worker/child/BootstrapSecurityManagerTest.groovy +++ b/subprojects/workers/src/test/groovy/org/gradle/process/internal/worker/child/BootstrapSecurityManagerTest.groovy @@ -67,6 +67,24 @@ class BootstrapSecurityManagerTest extends Specification { System.getProperty("java.class.path") == [entry1.absolutePath, entry2.absolutePath].join(File.pathSeparator) } + def "fails with proper error message if System.in is not delivering all expected data"() { + given: + def incompleteStream = new ByteArrayOutputStream() + def dataOut = new DataOutputStream(new EncodedStream.EncodedOutput(incompleteStream)) + dataOut.writeInt(1) // expect one classpath entry + dataOut.write(1234) // but the entry is not a complete UTF-8 encoded String + + System.in = new ByteArrayInputStream(incompleteStream.toByteArray()) + + when: + new BootstrapSecurityManager(new TestClassLoader()).checkPermission(new AllPermission()) + + then: + RuntimeException e = thrown() + e.message == "Could not initialise system classpath." + e.cause instanceof EOFException + } + def "installs custom SecurityManager"() { URLClassLoader cl = new URLClassLoader([] as URL[], getClass().classLoader) diff --git a/subprojects/workers/workers.gradle.kts b/subprojects/workers/workers.gradle.kts index bbdfc0cc13cf1..fb87aa319cae4 100644 --- a/subprojects/workers/workers.gradle.kts +++ b/subprojects/workers/workers.gradle.kts @@ -6,7 +6,6 @@ plugins { dependencies { compile(project(":core")) - compile(library("jcip")) integTestCompile(project(":internalIntegTesting")) testFixturesApi(project(":internalTesting")) diff --git a/version.txt b/version.txt index 48c32b26a12f0..e5e7441d3e938 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -5.4 \ No newline at end of file +5.5 \ No newline at end of file