From feb0c469481bc131217c68b0acd95081cc12c072 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 7 Oct 2018 01:14:59 +0200 Subject: [PATCH 01/73] Add support to snapshot releases --- core/build.gradle | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 1949d61..7d526fd 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,14 +1,44 @@ plugins { - id 'com.novoda.bintray-release' version "0.8.0" + id 'com.novoda.bintray-release' version '0.8.0' id 'groovy' id 'java-gradle-plugin' id 'maven' + id 'com.novoda.build-properties' version '0.4.1' } repositories { jcenter() } +version = '0.8.1' + +buildProperties { + cli { + using(project) + } + + bintray { + def bintrayCredentials = { + return isDryRun() ? + ['bintrayRepo': 'n/a', 'bintrayUser': 'n/a', 'bintrayKey': 'n/a'] : + new File("${System.getenv('BINTRAY_PROPERTIES')}") + } + using(bintrayCredentials()).or(cli) + description = '''This should contain the following properties: + | - bintrayRepo: name of the repo of the organisation to deploy the artifacts to + | - bintrayUser: name of the account used to deploy the artifacts + | - bintrayKey: API key of the account used to deploy the artifacts + '''.stripMargin() + } + + publish { + def generateVersion = { + return isSnapshot() ? "SNAPSHOT-${System.getenv('BUILD_NUMBER') ?: 'LOCAL'}" : project.version + } + using(['version': "${generateVersion()}"]).or(buildProperties.bintray) + } +} + dependencies { compile localGroovy() compile 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' @@ -39,7 +69,19 @@ publish { userOrg = 'novoda' groupId = 'com.novoda' artifactId = rootProject.name - publishVersion = '0.8.1' desc = 'Super duper easy way to release your Android and other artifacts to bintray' website = "https://github.com/novoda/${rootProject.name}" + + version = project.buildProperties.publish['version'].string + publishVersion = project.buildProperties.publish['version'].string + bintrayUser = project.buildProperties.publish['bintrayUser'].string + bintrayKey = project.buildProperties.publish['bintrayKey'].string + repoName = buildProperties.publish['bintrayRepo'].string +} + +boolean isDryRun() { + buildProperties.cli['dryRun'].or(true).boolean +} +boolean isSnapshot() { + buildProperties.cli['bintraySnapshot'].or(false).boolean } From 19cf76fc273ba354cb9c6259517b275500f5545c Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 7 Oct 2018 01:40:07 +0200 Subject: [PATCH 02/73] Update JFrog Bintray plugin to latest version (1.8.4) --- core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/build.gradle b/core/build.gradle index 7d526fd..2259d3e 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -41,7 +41,7 @@ buildProperties { dependencies { compile localGroovy() - compile 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + compile 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:3.9.0' From 01723a3ca399d74b31d7e1adf08ebc06c1064a2b Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 7 Oct 2018 01:40:18 +0200 Subject: [PATCH 03/73] Apply the BintrayPlugin outside afterEvaluate() to avoid issues with that plugin internal evaluation listener --- .../groovy/com/novoda/gradle/release/ReleasePlugin.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy b/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy index 5a82114..c75393b 100644 --- a/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy +++ b/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy @@ -12,11 +12,11 @@ class ReleasePlugin implements Plugin { PublishExtension extension = project.extensions.create('publish', PublishExtension) project.afterEvaluate { extension.validate() - project.apply([plugin: 'maven-publish']) attachArtifacts(extension, project) - new BintrayPlugin().apply(project) new BintrayConfiguration(extension).configure(project) } + project.apply([plugin: 'maven-publish']) + new BintrayPlugin().apply(project) } void attachArtifacts(PublishExtension extension, Project project) { From 9dd86bbb6a4c589087950d623c2c4ccb49db7f88 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 7 Oct 2018 01:40:50 +0200 Subject: [PATCH 04/73] Update CI script to include also placeholder user and key for Bintray --- ci/prBuilder.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/prBuilder.sh b/ci/prBuilder.sh index 74902f1..2756dd5 100755 --- a/ci/prBuilder.sh +++ b/ci/prBuilder.sh @@ -5,7 +5,7 @@ set -e BASEDIR=$(dirname "$0") # Testing the core plugin -cd $BASEDIR/../ && ./gradlew clean build bintrayUpload -PdryRun=true --info +cd $BASEDIR/../ && ./gradlew clean build bintrayUpload -PdryRun=true -PbintrayUser=user -PbintrayKey=key --info # Testing the samples -cd $BASEDIR/../samples/ && ./gradlew clean build bintrayUpload -PdryRun=true --info +cd $BASEDIR/../samples/ && ./gradlew clean build bintrayUpload -PdryRun=true -PbintrayUser=user -PbintrayKey=key --info From 82ab856f91f3dcb2dc6ff51fc14a6956efaf8a13 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 15 Oct 2018 23:28:13 +0200 Subject: [PATCH 05/73] Ignore output dir generated by IntelliJ IDE --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 0816a46..c18d7ca 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ .gradle .idea build +out local.properties From 0434998fae27ea03b1d291811f34d62a810c6ca3 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 15 Oct 2018 23:28:54 +0200 Subject: [PATCH 06/73] Fix assertions around generated POM file --- .../novoda/gradle/release/BuildFolder.groovy | 41 +++++++++++ .../gradle/release/TestGeneratePomTask.groovy | 68 +++++++++---------- 2 files changed, 74 insertions(+), 35 deletions(-) create mode 100644 core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy b/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy new file mode 100644 index 0000000..7a54c67 --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy @@ -0,0 +1,41 @@ +package com.novoda.gradle.release; + +import org.junit.rules.ExternalResource + +class BuildFolder extends ExternalResource { + + private File rootDir + + BuildFolder(String path = '') { + File buildDir = new File(getResource('.').file).parentFile.parentFile.parentFile + assert buildDir.path.endsWith('core/build') + rootDir = new File(buildDir, path) + rootDir.mkdirs() + } + + private static URL getResource(String resourceName) { + ClassLoader loader = Thread.currentThread().getContextClassLoader() ?: BuildFolder.class.getClassLoader() + URL url = loader.getResource(resourceName) + if (url == null) { + throw new IllegalArgumentException("resource ${resourceName} not found.") + } + return url + } + + File newFolder(String path) { + File folder = new File(rootDir, path) + folder.mkdirs() + return folder + } + + File newFile(File parent, String path) { + File file = new File(parent, path) + file.parentFile.mkdirs() + file.createNewFile() + return file + } + + File newFile(String path) { + return newFile(rootDir, path) + } +} diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy index 6053782..f9ba1a8 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy @@ -1,26 +1,21 @@ package com.novoda.gradle.release import org.gradle.testkit.runner.GradleRunner -import org.junit.Before import org.junit.Rule import org.junit.Test -import org.junit.rules.TemporaryFolder import static org.gradle.testkit.runner.TaskOutcome.SUCCESS class TestGeneratePomTask { - @Rule public TemporaryFolder testProjectDir = new TemporaryFolder() - private File buildFile - - @Before - void setup() { - buildFile = testProjectDir.newFile('build.gradle') - } + @Rule + public BuildFolder buildFolder = new BuildFolder('test-projects/TestGeneratePomTask') @Test void testGeneratePomTaskForJavaLib() { - buildFile << """ + def projectDir = buildFolder.newFolder('testGeneratePomTaskForJavaLib') + buildFolder.newFile(projectDir, 'build.gradle').write(''' + plugins { id 'java-library' id 'bintray-release' @@ -39,36 +34,38 @@ class TestGeneratePomTask { implementation 'com.xyz:world:2.0.0' api 'com.xxx:haha:3.0.0' } - """ + '''.stripMargin()) def result = GradleRunner.create() - .withProjectDir(testProjectDir.root) - .withArguments("generatePomFileForMavenPublication") - .withPluginClasspath() - .build() + .withProjectDir(projectDir) + .withArguments('generatePomFileForMavenPublication', '--stacktrace') + .forwardOutput() + .withPluginClasspath() + .build() - assert result.task(":generatePomFileForMavenPublication").outcome == SUCCESS + assert result.task(':generatePomFileForMavenPublication').outcome == SUCCESS - File pomFile = new File(testProjectDir.root, '/build/publications/maven/pom-default.xml') + File pomFile = new File(projectDir, '/build/publications/maven/pom-default.xml') def nodes = new XmlSlurper().parse(pomFile) def dependencies = nodes.dependencies.dependency assert dependencies.size() == 3 - assert dependencies.find { dep -> dep.artifactId == "hello" && dep.scope == "compile" } != null - assert dependencies.find { dep -> dep.artifactId == "haha" && dep.scope == "compile" } != null - assert dependencies.find { dep -> dep.artifactId == "world" && dep.scope == "runtime" } != null + assert dependencies.find { dep -> dep.artifactId == 'hello' }.scope == 'compile' + assert dependencies.find { dep -> dep.artifactId == 'haha' }.scope == 'compile' + assert dependencies.find { dep -> dep.artifactId == 'world' }.scope == 'runtime' } @Test void testGeneratePomTaskForAndroidLibrary() { - File manifestFile = new File(testProjectDir.root, "/src/main/AndroidManifest.xml") + def projectDir = buildFolder.newFolder('testGeneratePomTaskForAndroidLibrary') + File manifestFile = new File(projectDir, '/src/main/AndroidManifest.xml') manifestFile.getParentFile().mkdirs() manifestFile.createNewFile() - manifestFile << """ + manifestFile.write(''' - """ + '''.stripMargin()) - buildFile << """ + buildFolder.newFile(projectDir, 'build.gradle').write(''' buildscript { repositories { jcenter() @@ -110,23 +107,24 @@ class TestGeneratePomTask { implementation 'com.xyz:world:2.0.0' api 'com.xxx:haha:3.0.0' } - """ + '''.stripMargin()) def result = GradleRunner.create() - .withProjectDir(testProjectDir.root) - .withArguments("generatePomFileForReleasePublication") - .withPluginClasspath() - .build() + .withProjectDir(projectDir) + .withArguments('generatePomFileForReleasePublication', '--stacktrace') + .forwardOutput() + .withPluginClasspath() + .build() - assert result.task(":generatePomFileForReleasePublication").outcome == SUCCESS + assert result.task(':generatePomFileForReleasePublication').outcome == SUCCESS - File pomFile = new File(testProjectDir.root, '/build/publications/release/pom-default.xml') + File pomFile = new File(projectDir, '/build/publications/release/pom-default.xml') def nodes = new XmlSlurper().parse(pomFile) def dependencies = nodes.dependencies.dependency assert dependencies.size() == 3 - assert dependencies.find { dep -> dep.artifactId == "hello" && dep.scope == "compile" } != null - assert dependencies.find { dep -> dep.artifactId == "haha" && dep.scope == "compile" } != null - assert dependencies.find { dep -> dep.artifactId == "world" && dep.scope == "runtime" } != null + assert dependencies.find { dep -> dep.artifactId == 'hello' }.scope == 'compile' + assert dependencies.find { dep -> dep.artifactId == 'haha' }.scope == 'compile' + assert dependencies.find { dep -> dep.artifactId == 'world' }.scope == 'runtime' } -} \ No newline at end of file +} From 030b47110e66c523d6e71413a6adaa41c37c4d0d Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 16:45:10 +0200 Subject: [PATCH 07/73] Tweak BuildFolder rule to implement TestRule directly --- .../novoda/gradle/release/BuildFolder.groovy | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy b/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy index 7a54c67..c033b79 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy @@ -1,8 +1,11 @@ -package com.novoda.gradle.release; +package com.novoda.gradle.release -import org.junit.rules.ExternalResource -class BuildFolder extends ExternalResource { +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class BuildFolder implements TestRule { private File rootDir @@ -10,7 +13,6 @@ class BuildFolder extends ExternalResource { File buildDir = new File(getResource('.').file).parentFile.parentFile.parentFile assert buildDir.path.endsWith('core/build') rootDir = new File(buildDir, path) - rootDir.mkdirs() } private static URL getResource(String resourceName) { @@ -38,4 +40,20 @@ class BuildFolder extends ExternalResource { File newFile(String path) { return newFile(rootDir, path) } + + File getRoot() { + return rootDir + } + + @Override + Statement apply(Statement base, Description description) { + return new Statement() { + @Override + void evaluate() throws Throwable { + rootDir.mkdirs() + base.evaluate() + rootDir.delete() + } + } + } } From 3bc6c8d0568dbaf12bb58ebc7d0ba497793296c0 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 16:48:44 +0200 Subject: [PATCH 08/73] Make TestProjectRule to use BuildFolder in order to be able to inspect test projects if test fails --- .../AndroidDifferentGradleVersions.groovy | 2 +- .../JavaDifferentGradleVersions.groovy | 2 +- .../release/TestInvalidExtensionSetup.groovy | 4 +- .../release/rule/TestProjectRule.groovy | 44 ++++++++----------- 4 files changed, 21 insertions(+), 31 deletions(-) diff --git a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy index 46a1bdf..0d53c0c 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat class AndroidDifferentGradleVersions { @Rule - public TestProjectRule projectRule = new TestProjectRule(TestProjectRule.Project.ANDROID) + public TestProjectRule projectRule = TestProjectRule.newAndroidProject() @Parameterized.Parameters(name = "{index}: test Gradle version {0}") static Collection gradleVersionExpectedOutcome() { diff --git a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy index 0292fc7..99ea308 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy @@ -14,7 +14,7 @@ import static org.assertj.core.api.Assertions.assertThat class JavaDifferentGradleVersions { @Rule - public TestProjectRule projectRule = new TestProjectRule(TestProjectRule.Project.JAVA) + public TestProjectRule projectRule = TestProjectRule.newJavaProject() @Parameterized.Parameters(name = "{index}: test Gradle version {0}") static Collection gradleVersionExpectedOutcome() { diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy index f04aaa2..78aa097 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy @@ -1,8 +1,6 @@ package com.novoda.gradle.release import com.novoda.gradle.release.rule.TestProjectRule -import org.assertj.core.api.Assertions -import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.GradleRunner import org.junit.Rule import org.junit.Test @@ -30,7 +28,7 @@ class TestInvalidExtensionSetup { """ @Rule - public TestProjectRule projectRule = new TestProjectRule(TestProjectRule.Project.JAVA, buildScript) + public TestProjectRule projectRule = TestProjectRule.newJavaProject(buildScript) @Test void testInvalidExtension_versionAndDescMissing_ShouldFailWithCorrectMessage() { diff --git a/core/src/test/groovy/com/novoda/gradle/release/rule/TestProjectRule.groovy b/core/src/test/groovy/com/novoda/gradle/release/rule/TestProjectRule.groovy index 0814c1f..849b4b0 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/rule/TestProjectRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/rule/TestProjectRule.groovy @@ -1,53 +1,46 @@ package com.novoda.gradle.release.rule -import org.junit.rules.TemporaryFolder +import com.novoda.gradle.release.BuildFolder import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement class TestProjectRule implements TestRule { - enum Project { + private enum ProjectType { JAVA, ANDROID } - private def tempFolder = new TemporaryFolder() - - private final Project project - + private final ProjectType project + private BuildFolder tempFolder private String buildScript - /** - * Creates a new TestProjectRule with a default/valid BuildScript-Template. - */ - TestProjectRule(Project project) { - this.project = project + static TestProjectRule newJavaProject(String buildScript = null) { + return new TestProjectRule(ProjectType.JAVA, buildScript) } - /** - * Creates a new TestProjectRule with a given buildScript. - */ - TestProjectRule(Project project, String buildScript) { + static TestProjectRule newAndroidProject(String buildScript = null) { + return new TestProjectRule(ProjectType.ANDROID, buildScript) + } + + private TestProjectRule(ProjectType project, String buildScript) { this.project = project this.buildScript = buildScript } @Override Statement apply(Statement base, Description description) { - return new Statement() { + tempFolder = new BuildFolder("test-projects/${description.testClass.canonicalName}/${description.methodName}") + def statement = new Statement() { @Override void evaluate() throws Throwable { - tempFolder.create() createSourceCode() createAndroidManifest() createBuildScript() - try { - base.evaluate() - } finally { - tempFolder.delete() - } + base.evaluate() } } + return tempFolder.apply(statement, description) } File getProjectDir() { @@ -62,7 +55,7 @@ class TestProjectRule implements TestRule { } private void createAndroidManifest() { - if (project == Project.ANDROID) { + if (project == ProjectType.ANDROID) { new File(tempFolder.root, "/src/main/AndroidManifest.xml").with { getParentFile().mkdirs() text = "" @@ -81,15 +74,14 @@ class TestProjectRule implements TestRule { // ... otherwise use the Templates switch (project) { - case Project.JAVA: + case ProjectType.JAVA: gradleScript.text = GradleScriptTemplates.java() break - case Project.ANDROID: + case ProjectType.ANDROID: gradleScript.text = GradleScriptTemplates.android() break default: throw new IllegalArgumentException("$project should be a valid value!") } } - } From f0942c8f2ce2b3ce6476414bb2fca216a472e56d Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 17:05:48 +0200 Subject: [PATCH 09/73] Move templates in .test package --- .../release/{rule => test}/GradleScriptTemplates.groovy | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) rename core/src/test/groovy/com/novoda/gradle/release/{rule => test}/GradleScriptTemplates.groovy (94%) diff --git a/core/src/test/groovy/com/novoda/gradle/release/rule/GradleScriptTemplates.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy similarity index 94% rename from core/src/test/groovy/com/novoda/gradle/release/rule/GradleScriptTemplates.groovy rename to core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy index 424f857..eddb065 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/rule/GradleScriptTemplates.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy @@ -1,10 +1,7 @@ -package com.novoda.gradle.release.rule - -import groovy.transform.PackageScope +package com.novoda.gradle.release.test class GradleScriptTemplates { - @PackageScope static String java() { return """ plugins { @@ -30,7 +27,6 @@ class GradleScriptTemplates { """ } - @PackageScope static String android() { return """ buildscript { From 81b5758de951430e19c0fb5df14cdd825c4ae1ff Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 17:07:10 +0200 Subject: [PATCH 10/73] Move TestProjectRule to .test package too --- .../gradle/release/AndroidDifferentGradleVersions.groovy | 2 +- .../gradle/release/JavaDifferentGradleVersions.groovy | 2 +- .../novoda/gradle/release/TestInvalidExtensionSetup.groovy | 2 +- .../gradle/release/{rule => test}/TestProjectRule.groovy | 6 +++++- 4 files changed, 8 insertions(+), 4 deletions(-) rename core/src/test/groovy/com/novoda/gradle/release/{rule => test}/TestProjectRule.groovy (95%) diff --git a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy index 0d53c0c..72e882c 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy @@ -1,6 +1,6 @@ package com.novoda.gradle.release -import com.novoda.gradle.release.rule.TestProjectRule +import com.novoda.gradle.release.test.TestProjectRule import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.junit.Rule diff --git a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy index 99ea308..a4fcf5c 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy @@ -1,6 +1,6 @@ package com.novoda.gradle.release -import com.novoda.gradle.release.rule.TestProjectRule +import com.novoda.gradle.release.test.TestProjectRule import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.junit.Rule diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy index 78aa097..0dc208e 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy @@ -1,6 +1,6 @@ package com.novoda.gradle.release -import com.novoda.gradle.release.rule.TestProjectRule +import com.novoda.gradle.release.test.TestProjectRule import org.gradle.testkit.runner.GradleRunner import org.junit.Rule import org.junit.Test diff --git a/core/src/test/groovy/com/novoda/gradle/release/rule/TestProjectRule.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy similarity index 95% rename from core/src/test/groovy/com/novoda/gradle/release/rule/TestProjectRule.groovy rename to core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy index 849b4b0..7399fe0 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/rule/TestProjectRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy @@ -1,4 +1,4 @@ -package com.novoda.gradle.release.rule +package com.novoda.gradle.release.test import com.novoda.gradle.release.BuildFolder import org.junit.rules.TestRule @@ -84,4 +84,8 @@ class TestProjectRule implements TestRule { throw new IllegalArgumentException("$project should be a valid value!") } } + + String getProjectType() { + return project.name().toLowerCase() + } } From 350f087805df7f445f08de5ade51a45fba0a3abe Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 17:08:03 +0200 Subject: [PATCH 11/73] Rename BuildFolder to BuildFolderRule to keep consistent naming --- .../com/novoda/gradle/release/TestGeneratePomTask.groovy | 3 ++- .../{BuildFolder.groovy => test/BuildFolderRule.groovy} | 8 ++++---- .../com/novoda/gradle/release/test/TestProjectRule.groovy | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) rename core/src/test/groovy/com/novoda/gradle/release/{BuildFolder.groovy => test/BuildFolderRule.groovy} (88%) diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy index f9ba1a8..3547c71 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy @@ -1,5 +1,6 @@ package com.novoda.gradle.release +import com.novoda.gradle.release.test.BuildFolderRule import org.gradle.testkit.runner.GradleRunner import org.junit.Rule import org.junit.Test @@ -9,7 +10,7 @@ import static org.gradle.testkit.runner.TaskOutcome.SUCCESS class TestGeneratePomTask { @Rule - public BuildFolder buildFolder = new BuildFolder('test-projects/TestGeneratePomTask') + public BuildFolderRule buildFolder = new BuildFolderRule('test-projects/TestGeneratePomTask') @Test void testGeneratePomTaskForJavaLib() { diff --git a/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy similarity index 88% rename from core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy rename to core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy index c033b79..9720986 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/BuildFolder.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy @@ -1,22 +1,22 @@ -package com.novoda.gradle.release +package com.novoda.gradle.release.test import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -class BuildFolder implements TestRule { +class BuildFolderRule implements TestRule { private File rootDir - BuildFolder(String path = '') { + BuildFolderRule(String path = '') { File buildDir = new File(getResource('.').file).parentFile.parentFile.parentFile assert buildDir.path.endsWith('core/build') rootDir = new File(buildDir, path) } private static URL getResource(String resourceName) { - ClassLoader loader = Thread.currentThread().getContextClassLoader() ?: BuildFolder.class.getClassLoader() + ClassLoader loader = Thread.currentThread().getContextClassLoader() ?: BuildFolderRule.class.getClassLoader() URL url = loader.getResource(resourceName) if (url == null) { throw new IllegalArgumentException("resource ${resourceName} not found.") diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy index 7399fe0..c4b9694 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy @@ -1,6 +1,6 @@ package com.novoda.gradle.release.test -import com.novoda.gradle.release.BuildFolder + import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement @@ -12,7 +12,7 @@ class TestProjectRule implements TestRule { } private final ProjectType project - private BuildFolder tempFolder + private BuildFolderRule tempFolder private String buildScript static TestProjectRule newJavaProject(String buildScript = null) { @@ -30,7 +30,7 @@ class TestProjectRule implements TestRule { @Override Statement apply(Statement base, Description description) { - tempFolder = new BuildFolder("test-projects/${description.testClass.canonicalName}/${description.methodName}") + tempFolder = new BuildFolderRule("test-projects/${description.testClass.canonicalName}/${description.methodName}") def statement = new Statement() { @Override void evaluate() throws Throwable { From 40095a84cdfbfe85eb6992887951db46c5cea84a Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 17:54:24 +0200 Subject: [PATCH 12/73] Make BuildFolderRule work when running tests from IntelliJ --- .../novoda/gradle/release/test/BuildFolderRule.groovy | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy index 9720986..2ff71d7 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy @@ -10,9 +10,14 @@ class BuildFolderRule implements TestRule { private File rootDir BuildFolderRule(String path = '') { - File buildDir = new File(getResource('.').file).parentFile.parentFile.parentFile - assert buildDir.path.endsWith('core/build') - rootDir = new File(buildDir, path) + def start = new File(getResource('.').file) + if (start.path.endsWith('build/classes/groovy/test')) { + rootDir = new File(start.parentFile.parentFile.parentFile, path) + } else if (start.path.endsWith('out/test/classes')) { + rootDir = new File(start.parentFile.parentFile.parentFile, "build/$path") + } else { + throw new UnsupportedOperationException("Unable to identify build folder from path: $start") + } } private static URL getResource(String resourceName) { From 1e44bcc9ef4623779b176736c5b0c0970a9ddfe2 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 17:56:28 +0200 Subject: [PATCH 13/73] Use default templates as fallback parameter instead of checking manually --- .../release/test/GradleScriptTemplates.groovy | 8 +++---- .../release/test/TestProjectRule.groovy | 24 ++++--------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy index eddb065..dab0ef6 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy @@ -2,7 +2,7 @@ package com.novoda.gradle.release.test class GradleScriptTemplates { - static String java() { + static String forJavaProject() { return """ plugins { id 'java-library' @@ -24,10 +24,10 @@ class GradleScriptTemplates { publishVersion = '1.0' desc = 'description' } - """ + """.stripIndent() } - static String android() { + static String forAndroidProject() { return """ buildscript { repositories { @@ -76,6 +76,6 @@ class GradleScriptTemplates { publishVersion = '1.0' desc = 'description' } - """ + """.stripIndent() } } diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy index c4b9694..4196d62 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy @@ -15,11 +15,11 @@ class TestProjectRule implements TestRule { private BuildFolderRule tempFolder private String buildScript - static TestProjectRule newJavaProject(String buildScript = null) { + static TestProjectRule newJavaProject(String buildScript = GradleScriptTemplates.forJavaProject()) { return new TestProjectRule(ProjectType.JAVA, buildScript) } - static TestProjectRule newAndroidProject(String buildScript = null) { + static TestProjectRule newAndroidProject(String buildScript = GradleScriptTemplates.forAndroidProject()) { return new TestProjectRule(ProjectType.ANDROID, buildScript) } @@ -64,24 +64,8 @@ class TestProjectRule implements TestRule { } private void createBuildScript() { - File gradleScript = new File(tempFolder.root, "build.gradle") - - // If custom buildScript provided. Use it - if (buildScript != null) { - gradleScript.text = buildScript - return - } - - // ... otherwise use the Templates - switch (project) { - case ProjectType.JAVA: - gradleScript.text = GradleScriptTemplates.java() - break - case ProjectType.ANDROID: - gradleScript.text = GradleScriptTemplates.android() - break - default: - throw new IllegalArgumentException("$project should be a valid value!") + new File(tempFolder.root, "build.gradle").with { + text = buildScript } } From 4a03df97efd6f0c0d0c0c98f5e8863bf8b88a859 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 17:58:26 +0200 Subject: [PATCH 14/73] Rewrite the test checking the POM generation in order to use TestProjectRule --- ...eratePomFileForMavenPublicationTest.groovy | 77 ++++++++++ .../gradle/release/TestGeneratePomTask.groovy | 131 ------------------ 2 files changed, 77 insertions(+), 131 deletions(-) create mode 100644 core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy delete mode 100644 core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy new file mode 100644 index 0000000..352ebda --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy @@ -0,0 +1,77 @@ +package com.novoda.gradle.release + +import com.novoda.gradle.release.test.GradleScriptTemplates +import com.novoda.gradle.release.test.TestProjectRule +import org.gradle.testkit.runner.GradleRunner +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS + +@RunWith(Parameterized.class) +class GeneratePomFileForMavenPublicationTest { + + @Parameterized.Parameters(name = "{index}: test POM descriptor for {0}") + static Collection gradleVersionExpectedOutcome() { + return [ + new Parameter(TestProjectRule.newJavaProject(templateFrom(GradleScriptTemplates.forJavaProject()))), + new Parameter(TestProjectRule.newAndroidProject(templateFrom(GradleScriptTemplates.forAndroidProject()))) + ] + } + + @Rule + public TestProjectRule testProject + + GeneratePomFileForMavenPublicationTest(Parameter parameter) { + this.testProject = parameter.rule + } + + @Test + void shouldContainAllNeededDependenciesInGeneratePomTask() { + def projectDir = testProject.projectDir + + def result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments('generatePomFileForMavenPublication', '--stacktrace') + .forwardOutput() + .withPluginClasspath() + .build() + + assert result.task(':generatePomFileForMavenPublication').outcome == SUCCESS + + File pomFile = new File(projectDir, '/build/publications/maven/pom-default.xml') + def nodes = new XmlSlurper().parse(pomFile) + def dependencies = nodes.dependencies.dependency + + assert dependencies.find { dep -> dep.artifactId == 'hello' }.scope == 'compile' + assert dependencies.find { dep -> dep.artifactId == 'haha' }.scope == 'compile' + assert dependencies.find { dep -> dep.artifactId == 'world' }.scope == 'runtime' + } + + private static final String templateFrom(String baseTemplate) { + return """ + $baseTemplate + + dependencies { + compile 'com.abc:hello:1.0.0' + implementation 'com.xyz:world:2.0.0' + api 'com.xxx:haha:3.0.0' + } + """.stripIndent() + + } + + private static class Parameter { + final TestProjectRule rule + + Parameter(TestProjectRule rule) { + this.rule = rule + } + + String toString() { + return rule.projectType + } + } +} diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy deleted file mode 100644 index 3547c71..0000000 --- a/core/src/test/groovy/com/novoda/gradle/release/TestGeneratePomTask.groovy +++ /dev/null @@ -1,131 +0,0 @@ -package com.novoda.gradle.release - -import com.novoda.gradle.release.test.BuildFolderRule -import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.Test - -import static org.gradle.testkit.runner.TaskOutcome.SUCCESS - -class TestGeneratePomTask { - - @Rule - public BuildFolderRule buildFolder = new BuildFolderRule('test-projects/TestGeneratePomTask') - - @Test - void testGeneratePomTaskForJavaLib() { - def projectDir = buildFolder.newFolder('testGeneratePomTaskForJavaLib') - buildFolder.newFile(projectDir, 'build.gradle').write(''' - - plugins { - id 'java-library' - id 'bintray-release' - } - - publish { - userOrg = 'novoda' - groupId = 'com.novoda' - artifactId = 'test' - publishVersion = '1.0' - desc = 'description' - } - - dependencies { - compile 'com.abc:hello:1.0.0' - implementation 'com.xyz:world:2.0.0' - api 'com.xxx:haha:3.0.0' - } - '''.stripMargin()) - - def result = GradleRunner.create() - .withProjectDir(projectDir) - .withArguments('generatePomFileForMavenPublication', '--stacktrace') - .forwardOutput() - .withPluginClasspath() - .build() - - assert result.task(':generatePomFileForMavenPublication').outcome == SUCCESS - - File pomFile = new File(projectDir, '/build/publications/maven/pom-default.xml') - def nodes = new XmlSlurper().parse(pomFile) - def dependencies = nodes.dependencies.dependency - - assert dependencies.size() == 3 - assert dependencies.find { dep -> dep.artifactId == 'hello' }.scope == 'compile' - assert dependencies.find { dep -> dep.artifactId == 'haha' }.scope == 'compile' - assert dependencies.find { dep -> dep.artifactId == 'world' }.scope == 'runtime' - } - - @Test - void testGeneratePomTaskForAndroidLibrary() { - def projectDir = buildFolder.newFolder('testGeneratePomTaskForAndroidLibrary') - File manifestFile = new File(projectDir, '/src/main/AndroidManifest.xml') - manifestFile.getParentFile().mkdirs() - manifestFile.createNewFile() - manifestFile.write(''' - - '''.stripMargin()) - - buildFolder.newFile(projectDir, 'build.gradle').write(''' - buildscript { - repositories { - jcenter() - google() - } - dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' - } - } - - plugins { - id 'bintray-release' apply false - } - - apply plugin: "com.android.library" - apply plugin: "bintray-release" - - android { - compileSdkVersion 26 - buildToolsVersion "26.0.2" - - defaultConfig { - minSdkVersion 16 - versionCode 1 - versionName "0.0.1" - } - } - - publish { - userOrg = 'novoda' - groupId = 'com.novoda' - artifactId = 'test' - publishVersion = '1.0' - desc = 'description' - } - - dependencies { - compile 'com.abc:hello:1.0.0' - implementation 'com.xyz:world:2.0.0' - api 'com.xxx:haha:3.0.0' - } - '''.stripMargin()) - - def result = GradleRunner.create() - .withProjectDir(projectDir) - .withArguments('generatePomFileForReleasePublication', '--stacktrace') - .forwardOutput() - .withPluginClasspath() - .build() - - assert result.task(':generatePomFileForReleasePublication').outcome == SUCCESS - - File pomFile = new File(projectDir, '/build/publications/release/pom-default.xml') - def nodes = new XmlSlurper().parse(pomFile) - def dependencies = nodes.dependencies.dependency - - assert dependencies.size() == 3 - assert dependencies.find { dep -> dep.artifactId == 'hello' }.scope == 'compile' - assert dependencies.find { dep -> dep.artifactId == 'haha' }.scope == 'compile' - assert dependencies.find { dep -> dep.artifactId == 'world' }.scope == 'runtime' - } -} From 7f77ea362015c3bf2a783a4d7c6d2b4044dc6c09 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 18:23:21 +0200 Subject: [PATCH 15/73] Inject publication name in test parameter --- ...eratePomFileForMavenPublicationTest.groovy | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy index 352ebda..eeb8e64 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy @@ -15,33 +15,34 @@ class GeneratePomFileForMavenPublicationTest { @Parameterized.Parameters(name = "{index}: test POM descriptor for {0}") static Collection gradleVersionExpectedOutcome() { - return [ - new Parameter(TestProjectRule.newJavaProject(templateFrom(GradleScriptTemplates.forJavaProject()))), - new Parameter(TestProjectRule.newAndroidProject(templateFrom(GradleScriptTemplates.forAndroidProject()))) - ] + return [Parameter.forJavaProject(), Parameter.forAndroidProject()] } @Rule - public TestProjectRule testProject + public final TestProjectRule testProject + + private final String publicationName GeneratePomFileForMavenPublicationTest(Parameter parameter) { this.testProject = parameter.rule + this.publicationName = parameter.publicationName } @Test void shouldContainAllNeededDependenciesInGeneratePomTask() { def projectDir = testProject.projectDir + def generatingTaskName = ":generatePomFileFor${publicationName.capitalize()}Publication" def result = GradleRunner.create() .withProjectDir(projectDir) - .withArguments('generatePomFileForMavenPublication', '--stacktrace') + .withArguments(generatingTaskName, '--stacktrace') .forwardOutput() .withPluginClasspath() .build() - assert result.task(':generatePomFileForMavenPublication').outcome == SUCCESS + assert result.task(generatingTaskName).outcome == SUCCESS - File pomFile = new File(projectDir, '/build/publications/maven/pom-default.xml') + File pomFile = new File(projectDir, "/build/publications/$publicationName/pom-default.xml") def nodes = new XmlSlurper().parse(pomFile) def dependencies = nodes.dependencies.dependency @@ -50,8 +51,22 @@ class GeneratePomFileForMavenPublicationTest { assert dependencies.find { dep -> dep.artifactId == 'world' }.scope == 'runtime' } - private static final String templateFrom(String baseTemplate) { - return """ + private static class Parameter { + + static Parameter forJavaProject() { + return new Parameter( + TestProjectRule.newJavaProject(templateFrom(GradleScriptTemplates.forJavaProject())), + 'maven') + } + + static Parameter forAndroidProject() { + return new Parameter( + TestProjectRule.newAndroidProject(templateFrom(GradleScriptTemplates.forAndroidProject())), + 'release') + } + + private static final String templateFrom(String baseTemplate) { + return """ $baseTemplate dependencies { @@ -61,13 +76,14 @@ class GeneratePomFileForMavenPublicationTest { } """.stripIndent() - } + } - private static class Parameter { final TestProjectRule rule + final String publicationName - Parameter(TestProjectRule rule) { + Parameter(TestProjectRule rule, String publicationName) { this.rule = rule + this.publicationName = publicationName } String toString() { From 7d4a099e94afe4ffb6a85ebf6c2c7cba7013abe8 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 19:58:47 +0200 Subject: [PATCH 16/73] Generates settings.gradle with default project name to void tests to use the generated one --- .../com/novoda/gradle/release/test/TestProjectRule.groovy | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy index 4196d62..a8e0770 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy @@ -37,6 +37,7 @@ class TestProjectRule implements TestRule { createSourceCode() createAndroidManifest() createBuildScript() + createSettingsScript() base.evaluate() } } @@ -69,6 +70,12 @@ class TestProjectRule implements TestRule { } } + private void createSettingsScript() { + new File(tempFolder.root, 'settings.gradle').with { + text = "rootProject.name = 'test'" + } + } + String getProjectType() { return project.name().toLowerCase() } From 3c78d3e9b8f80f197717e1f2225123871aea7dfe Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 21:25:13 +0200 Subject: [PATCH 17/73] Use AssertJ constructs instead of plain assert --- .../GeneratePomFileForMavenPublicationTest.groovy | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy index eeb8e64..4386e93 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy @@ -8,6 +8,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import static org.assertj.core.api.Assertions.assertThat import static org.gradle.testkit.runner.TaskOutcome.SUCCESS @RunWith(Parameterized.class) @@ -40,15 +41,15 @@ class GeneratePomFileForMavenPublicationTest { .withPluginClasspath() .build() - assert result.task(generatingTaskName).outcome == SUCCESS + assertThat(result.task(generatingTaskName).outcome).isEqualTo(SUCCESS) File pomFile = new File(projectDir, "/build/publications/$publicationName/pom-default.xml") def nodes = new XmlSlurper().parse(pomFile) def dependencies = nodes.dependencies.dependency - assert dependencies.find { dep -> dep.artifactId == 'hello' }.scope == 'compile' - assert dependencies.find { dep -> dep.artifactId == 'haha' }.scope == 'compile' - assert dependencies.find { dep -> dep.artifactId == 'world' }.scope == 'runtime' + assertThat(dependencies.find { dep -> dep.artifactId == 'hello' }.scope).isEqualTo('compile') + assertThat(dependencies.find { dep -> dep.artifactId == 'haha' }.scope).isEqualTo('compile') + assertThat(dependencies.find { dep -> dep.artifactId == 'world' }.scope).isEqualTo('runtime') } private static class Parameter { From d88ff140b747872974f01d253b0166d7fff875f3 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 22:02:24 +0200 Subject: [PATCH 18/73] Use Truth as assertion framework --- core/build.gradle | 10 +++++----- .../release/AndroidDifferentGradleVersions.groovy | 2 +- .../GeneratePomFileForMavenPublicationTest.groovy | 2 +- .../gradle/release/JavaDifferentGradleVersions.groovy | 2 +- .../gradle/release/TestInvalidExtensionSetup.groovy | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 2259d3e..72ce7f5 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -44,7 +44,7 @@ dependencies { compile 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' testCompile 'junit:junit:4.12' - testCompile 'org.assertj:assertj-core:3.9.0' + testCompile 'com.google.truth:truth:0.42' } compileGroovy { @@ -55,12 +55,12 @@ compileGroovy { gradlePlugin { plugins { binrayRelease { - id = "com.novoda.bintray-release" - implementationClass = "com.novoda.gradle.release.ReleasePlugin" + id = 'com.novoda.bintray-release' + implementationClass = 'com.novoda.gradle.release.ReleasePlugin' } legacy { - id = "bintray-release" - implementationClass = "com.novoda.gradle.release.ReleasePlugin" + id = 'bintray-release' + implementationClass = 'com.novoda.gradle.release.ReleasePlugin' } } } diff --git a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy index 72e882c..2757814 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy @@ -8,7 +8,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import static org.assertj.core.api.Assertions.assertThat +import static com.google.common.truth.Truth.assertThat @RunWith(Parameterized.class) class AndroidDifferentGradleVersions { diff --git a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy index 4386e93..c6914a6 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy @@ -8,7 +8,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import static org.assertj.core.api.Assertions.assertThat +import static com.google.common.truth.Truth.assertThat import static org.gradle.testkit.runner.TaskOutcome.SUCCESS @RunWith(Parameterized.class) diff --git a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy index a4fcf5c..7610cb5 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy @@ -8,7 +8,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import static org.assertj.core.api.Assertions.assertThat +import static com.google.common.truth.Truth.assertThat @RunWith(Parameterized.class) class JavaDifferentGradleVersions { diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy index 0dc208e..383ceee 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy @@ -7,7 +7,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -import static org.assertj.core.api.Assertions.* +import static com.google.common.truth.Truth.assertThat @RunWith(JUnit4.class) class TestInvalidExtensionSetup { From e188f1776641cb629f230ba1f3e645eaaeb877c9 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 19:57:37 +0200 Subject: [PATCH 19/73] Tweak java gradle development setup --- build.gradle | 14 ++++++++++++++ core/build.gradle | 9 ++++----- .../release/test/GradleScriptTemplates.groovy | 3 +-- settings.gradle | 14 -------------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/build.gradle b/build.gradle index e69de29..1f8d0a2 100644 --- a/build.gradle +++ b/build.gradle @@ -0,0 +1,14 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.novoda:bintray-release:0.8.0' + } +} + +subprojects { + repositories { + jcenter() + } +} diff --git a/core/build.gradle b/core/build.gradle index 72ce7f5..438eb2b 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -1,17 +1,14 @@ plugins { - id 'com.novoda.bintray-release' version '0.8.0' id 'groovy' id 'java-gradle-plugin' id 'maven' id 'com.novoda.build-properties' version '0.4.1' } -repositories { - jcenter() -} - version = '0.8.1' +apply plugin: 'com.novoda.bintray-release' + buildProperties { cli { using(project) @@ -45,6 +42,8 @@ dependencies { testCompile 'junit:junit:4.12' testCompile 'com.google.truth:truth:0.42' + + testRuntime files(pluginUnderTestMetadata) } compileGroovy { diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy index dab0ef6..a2d3212 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy @@ -40,11 +40,10 @@ class GradleScriptTemplates { } plugins { - id 'com.novoda.bintray-release' apply false + id 'com.novoda.bintray-release' } apply plugin: "com.android.library" - apply plugin: "com.novoda.bintray-release" android { compileSdkVersion 26 diff --git a/settings.gradle b/settings.gradle index 89a3a03..1820e64 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,16 +1,2 @@ -pluginManagement { - repositories { - // TODO: Update later to gradlePluginPortal() if gradle updated - maven { url 'https://plugins.gradle.org/m2/' } - } - resolutionStrategy { - eachPlugin { - if (requested.id.id == "com.novoda.bintray-release") { - useModule("com.novoda:bintray-release:${requested.version}") - } - } - } -} - include ':core' rootProject.name = 'bintray-release' \ No newline at end of file From 619684740a39d4fcad890034d750e2397c77b620 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 20 Oct 2018 19:57:58 +0200 Subject: [PATCH 20/73] Rewrite upload task tests using TestProjectRule --- .../gradle/release/BintrayUploadTest.groovy | 64 +++++++++++++++++++ .../release/TestBintrayUploadTask.groovy | 46 ------------- 2 files changed, 64 insertions(+), 46 deletions(-) create mode 100644 core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy delete mode 100644 core/src/test/groovy/com/novoda/gradle/release/TestBintrayUploadTask.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy new file mode 100644 index 0000000..2391478 --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy @@ -0,0 +1,64 @@ +package com.novoda.gradle.release + + +import com.novoda.gradle.release.test.TestProjectRule +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +import static com.google.common.truth.Truth.assertThat + +@RunWith(Parameterized.class) +class BintrayUploadTest { + + @Parameterized.Parameters(name = "{index}: test upload task for {0}") + static Collection gradleVersionExpectedOutcome() { + return [Parameter.forJavaProject(), Parameter.forAndroidProject()] + } + + @Rule + public final TestProjectRule testProject + + BintrayUploadTest(Parameter parameter) { + this.testProject = parameter.rule + } + + @Test + void shouldUploadSuccessfullyAsDryRun() { + def projectDir = testProject.projectDir + def uploadTaskName = ":bintrayUpload" + + def result = GradleRunner.create() + .withProjectDir(projectDir) + .withArguments('build', uploadTaskName, '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') + .forwardOutput() + .withPluginClasspath() + .build() + + assertThat(result.task(uploadTaskName).outcome).isEqualTo(TaskOutcome.SUCCESS) + } + + private static class Parameter { + + static Parameter forJavaProject() { + return new Parameter(TestProjectRule.newJavaProject()) + } + + static Parameter forAndroidProject() { + return new Parameter(TestProjectRule.newAndroidProject()) + } + + final TestProjectRule rule + + Parameter(TestProjectRule rule) { + this.rule = rule + } + + String toString() { + return rule.projectType + } + } +} diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestBintrayUploadTask.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestBintrayUploadTask.groovy deleted file mode 100644 index 9f8af69..0000000 --- a/core/src/test/groovy/com/novoda/gradle/release/TestBintrayUploadTask.groovy +++ /dev/null @@ -1,46 +0,0 @@ -package com.novoda.gradle.release - -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.BuildTask -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome -import org.junit.Test - -public class TestBintrayUploadTask { - - @Test - public void testBintrayUploadTask() { - BuildResult result = runTasksOnBintrayReleasePlugin('-PbintrayUser=U', '-PbintrayKey=K', "bintrayUpload") - - TaskOutcome success = TaskOutcome.SUCCESS - List tasks = result.tasks(success) - List successfulTaskPaths = new ArrayList<>(); - for (BuildTask task : tasks) { - successfulTaskPaths.add(task.path) - } - - assert successfulTaskPaths.contains(":bintrayUpload") - assert result.getOutput().contains("BUILD SUCCESSFUL") - } - - BuildResult runTasksOnBintrayReleasePlugin(String... arguments) { - File file = getAbsoluteDirectoryOfOurProjectBase() - - GradleRunner runner = GradleRunner.create() - .withProjectDir(file) - .withPluginClasspath() - - if (arguments) { - runner.withArguments(arguments) - } - return runner.build() - } - - /** - * Get a path that is absolute when running this test from the IDE or the CMD line and work back from there - */ - private File getAbsoluteDirectoryOfOurProjectBase() { - def fileDir = getClass().protectionDomain.codeSource.location.path - return new File(fileDir + "../../../..") - } -} From ac0e389f5b2e8a2dcb83405f36a766a65f8d3040 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 10:30:47 +0200 Subject: [PATCH 21/73] Introduce BuildResult wrapper --- .../release/test/TestProjectRule.groovy | 17 +++++++- .../gradle/test/GradleBuildResult.groovy | 43 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 core/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy index a8e0770..662d3b2 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy @@ -1,6 +1,8 @@ package com.novoda.gradle.release.test - +import com.novoda.gradle.test.GradleBuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.UnexpectedBuildFailure import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement @@ -79,4 +81,17 @@ class TestProjectRule implements TestRule { String getProjectType() { return project.name().toLowerCase() } + + GradleBuildResult execute(String... arguments) { + def runner = GradleRunner.create() + .forwardOutput() + .withPluginClasspath() + .withProjectDir(projectDir) + .withArguments(arguments) + try { + return new GradleBuildResult(runner.build(), true) + } catch (UnexpectedBuildFailure e) { + return new GradleBuildResult(e.buildResult, false) + } + } } diff --git a/core/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy b/core/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy new file mode 100644 index 0000000..9e3ab4c --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy @@ -0,0 +1,43 @@ +package com.novoda.gradle.test + + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.BuildTask +import org.gradle.testkit.runner.TaskOutcome + +class GradleBuildResult implements BuildResult { + + final boolean success + private final BuildResult buildResult + + GradleBuildResult(BuildResult buildResult, boolean success = true) { + this.buildResult = buildResult + this.success = success + } + + @Override + String getOutput() { + return buildResult.getOutput() + } + + @Override + List getTasks() { + return buildResult.getTasks() + } + + @Override + List tasks(TaskOutcome taskOutcome) { + return buildResult.tasks(taskOutcome) + } + + @Override + List taskPaths(TaskOutcome taskOutcome) { + return buildResult.taskPaths(taskOutcome) + } + + @Override + BuildTask task(String s) { + return buildResult.task(s) + } +} + From e237fd3c5d697e113f8f76d4a087ff1e35e7435e Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 10:31:41 +0200 Subject: [PATCH 22/73] Introduce Truth extensions to assert on gradle build results --- .../gradle/truth/BuildTaskSubject.groovy | 22 ++++++++++++++ .../truth/GradleBuildResultSubject.groovy | 29 +++++++++++++++++++ .../novoda/gradle/truth/GradleTruth.groovy | 17 +++++++++++ 3 files changed, 68 insertions(+) create mode 100644 core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy create mode 100644 core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy create mode 100644 core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy b/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy new file mode 100644 index 0000000..38f846c --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy @@ -0,0 +1,22 @@ +package com.novoda.gradle.truth + +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import org.checkerframework.checker.nullness.compatqual.NullableDecl +import org.gradle.testkit.runner.BuildTask +import org.gradle.testkit.runner.TaskOutcome + +class BuildTaskSubject extends Subject { + + public static final Subject.Factory FACTORY = { metadata, actual -> + return new BuildTaskSubject(metadata, actual) + } + + private BuildTaskSubject(FailureMetadata metadata, @NullableDecl BuildTask actual) { + super(metadata, actual) + } + + void hasOutcome(TaskOutcome outcome) { + check().that(actual().outcome).isEqualTo(outcome) + } +} diff --git a/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy b/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy new file mode 100644 index 0000000..7f36e09 --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy @@ -0,0 +1,29 @@ +package com.novoda.gradle.truth + +import com.google.common.truth.FailureMetadata +import com.google.common.truth.Subject +import com.novoda.gradle.test.GradleBuildResult +import org.checkerframework.checker.nullness.compatqual.NullableDecl + +class GradleBuildResultSubject extends Subject { + + public static final Subject.Factory FACTORY = { metadata, actual -> + return new GradleBuildResultSubject(metadata, actual) + } + + private GradleBuildResultSubject(FailureMetadata metadata, @NullableDecl GradleBuildResult actual) { + super(metadata, actual) + } + + void isSuccess() { + check().that(actual().success).isTrue() + } + + void isFailure() { + check().that(actual().success).isFalse() + } + + void containsOutput(String output) { + check().that(actual().output).contains(output) + } +} diff --git a/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy b/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy new file mode 100644 index 0000000..1a53b31 --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy @@ -0,0 +1,17 @@ +package com.novoda.gradle.truth + +import com.novoda.gradle.test.GradleBuildResult +import org.gradle.testkit.runner.BuildTask + +import static com.google.common.truth.Truth.assertAbout + +final class GradleTruth { + + static GradleBuildResultSubject assertThat(GradleBuildResult result) { + return assertAbout(GradleBuildResultSubject.FACTORY).that(result) + } + + static BuildTaskSubject assertThat(BuildTask actual) { + return assertAbout(BuildTaskSubject.FACTORY).that(actual) + } +} From af8c879991d8ae9db0d879b442446c2ae99181b5 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 10:40:26 +0200 Subject: [PATCH 23/73] Use Truth extensions and TestProjectRule.execute() in tests --- .../gradle/release/BintrayUploadTest.groovy | 16 ++----- ...eratePomFileForMavenPublicationTest.groovy | 29 ++++++------- .../release/InvalidExtensionSetupTest.groovy | 35 +++++++++++++++ .../release/TestInvalidExtensionSetup.groovy | 43 ------------------- 4 files changed, 53 insertions(+), 70 deletions(-) create mode 100644 core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy delete mode 100644 core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy index 2391478..58107b4 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy @@ -1,16 +1,13 @@ package com.novoda.gradle.release - import com.novoda.gradle.release.test.TestProjectRule -import org.gradle.testkit.runner.GradleRunner +import com.novoda.gradle.truth.GradleTruth import org.gradle.testkit.runner.TaskOutcome import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized -import static com.google.common.truth.Truth.assertThat - @RunWith(Parameterized.class) class BintrayUploadTest { @@ -28,17 +25,12 @@ class BintrayUploadTest { @Test void shouldUploadSuccessfullyAsDryRun() { - def projectDir = testProject.projectDir def uploadTaskName = ":bintrayUpload" - def result = GradleRunner.create() - .withProjectDir(projectDir) - .withArguments('build', uploadTaskName, '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') - .forwardOutput() - .withPluginClasspath() - .build() + def result = testProject.execute('build', uploadTaskName, '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') - assertThat(result.task(uploadTaskName).outcome).isEqualTo(TaskOutcome.SUCCESS) + GradleTruth.assertThat(result).isSuccess() + GradleTruth.assertThat(result.task(uploadTaskName)).hasOutcome(TaskOutcome.SUCCESS) } private static class Parameter { diff --git a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy index c6914a6..a93be34 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy @@ -2,7 +2,7 @@ package com.novoda.gradle.release import com.novoda.gradle.release.test.GradleScriptTemplates import com.novoda.gradle.release.test.TestProjectRule -import org.gradle.testkit.runner.GradleRunner +import com.novoda.gradle.truth.GradleTruth import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -31,25 +31,24 @@ class GeneratePomFileForMavenPublicationTest { @Test void shouldContainAllNeededDependenciesInGeneratePomTask() { - def projectDir = testProject.projectDir def generatingTaskName = ":generatePomFileFor${publicationName.capitalize()}Publication" - def result = GradleRunner.create() - .withProjectDir(projectDir) - .withArguments(generatingTaskName, '--stacktrace') - .forwardOutput() - .withPluginClasspath() - .build() + def result = testProject.execute(generatingTaskName, '--stacktrace') - assertThat(result.task(generatingTaskName).outcome).isEqualTo(SUCCESS) + GradleTruth.assertThat(result.task(generatingTaskName)).hasOutcome(SUCCESS) + assertThat(dependencyScopeFor('hello')).isEqualTo('compile') + assertThat(dependencyScopeFor('haha')).isEqualTo('compile') + assertThat(dependencyScopeFor('world')).isEqualTo('runtime') + } - File pomFile = new File(projectDir, "/build/publications/$publicationName/pom-default.xml") - def nodes = new XmlSlurper().parse(pomFile) - def dependencies = nodes.dependencies.dependency + private def dependencyScopeFor(String artifactId) { + return dependenciesFromPOM().find { dep -> dep.artifactId == artifactId }.scope + } - assertThat(dependencies.find { dep -> dep.artifactId == 'hello' }.scope).isEqualTo('compile') - assertThat(dependencies.find { dep -> dep.artifactId == 'haha' }.scope).isEqualTo('compile') - assertThat(dependencies.find { dep -> dep.artifactId == 'world' }.scope).isEqualTo('runtime') + private def dependenciesFromPOM() { + File pomFile = new File(testProject.projectDir, "/build/publications/$publicationName/pom-default.xml") + def nodes = new XmlSlurper().parse(pomFile) + return nodes.dependencies.dependency } private static class Parameter { diff --git a/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy new file mode 100644 index 0000000..9cf7b95 --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy @@ -0,0 +1,35 @@ +package com.novoda.gradle.release + +import com.novoda.gradle.release.test.TestProjectRule +import com.novoda.gradle.truth.GradleTruth +import org.junit.Rule +import org.junit.Test + +class InvalidExtensionSetupTest { + + private String buildScript = """ + plugins { + id 'java-library' + id 'com.novoda.bintray-release' + } + + publish { + userOrg = 'novoda' + groupId = 'com.novoda' + artifactId = 'test' + // publishVersion = '1.0' + // desc = 'description' + } + """.stripIndent() + + @Rule + public TestProjectRule testProject = TestProjectRule.newJavaProject(buildScript) + + @Test + void shouldFailWhenMissingMandatoryAttributes() { + def result = testProject.execute(':build') + + GradleTruth.assertThat(result).isFailure() + GradleTruth.assertThat(result).containsOutput("Have you created the publish closure? Missing publishVersion. Missing desc. ") + } +} diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy deleted file mode 100644 index 383ceee..0000000 --- a/core/src/test/groovy/com/novoda/gradle/release/TestInvalidExtensionSetup.groovy +++ /dev/null @@ -1,43 +0,0 @@ -package com.novoda.gradle.release - -import com.novoda.gradle.release.test.TestProjectRule -import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.JUnit4 - -import static com.google.common.truth.Truth.assertThat - -@RunWith(JUnit4.class) -class TestInvalidExtensionSetup { - - private String buildScript = """ - plugins { - id 'java-library' - id 'com.novoda.bintray-release' - } - - publish { - userOrg = 'novoda' - groupId = 'com.novoda' - artifactId = 'test' - // publishVersion = '1.0' - // desc = 'description' - } - """ - - @Rule - public TestProjectRule projectRule = TestProjectRule.newJavaProject(buildScript) - - @Test - void testInvalidExtension_versionAndDescMissing_ShouldFailWithCorrectMessage() { - def result = GradleRunner.create() - .withProjectDir(projectRule.projectDir) - .withArguments("build") - .withPluginClasspath() - .buildAndFail() - - assertThat(result.output).contains("Have you created the publish closure? Missing publishVersion. Missing desc. ") - } -} From 70aff993b5f91298519f6cdf0996e0542f7b0035 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 10:59:23 +0200 Subject: [PATCH 24/73] Ignore broken test as already fixed as part of a follow-up PR --- .../com/novoda/gradle/release/TestBintrayUploadTask.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/test/groovy/com/novoda/gradle/release/TestBintrayUploadTask.groovy b/core/src/test/groovy/com/novoda/gradle/release/TestBintrayUploadTask.groovy index 9f8af69..821b5ff 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/TestBintrayUploadTask.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/TestBintrayUploadTask.groovy @@ -4,8 +4,10 @@ import org.gradle.testkit.runner.BuildResult import org.gradle.testkit.runner.BuildTask import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome +import org.junit.Ignore import org.junit.Test +@Ignore public class TestBintrayUploadTask { @Test From 685d582d52f72637e7943140ed031b6854138f2a Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 14:52:05 +0200 Subject: [PATCH 25/73] Moving all test utilities in correct package --- .../gradle/release/AndroidDifferentGradleVersions.groovy | 3 ++- .../groovy/com/novoda/gradle/release/BintrayUploadTest.groovy | 2 +- .../release/GeneratePomFileForMavenPublicationTest.groovy | 4 ++-- .../novoda/gradle/release/InvalidExtensionSetupTest.groovy | 2 +- .../novoda/gradle/release/JavaDifferentGradleVersions.groovy | 3 ++- .../novoda/gradle/{release => }/test/BuildFolderRule.groovy | 2 +- .../gradle/{release => }/test/GradleScriptTemplates.groovy | 2 +- .../gradle/{release => test}/GradleVerionsParams.groovy | 2 +- .../novoda/gradle/{release => }/test/TestProjectRule.groovy | 4 ++-- 9 files changed, 13 insertions(+), 11 deletions(-) rename core/src/test/groovy/com/novoda/gradle/{release => }/test/BuildFolderRule.groovy (97%) rename core/src/test/groovy/com/novoda/gradle/{release => }/test/GradleScriptTemplates.groovy (98%) rename core/src/test/groovy/com/novoda/gradle/{release => test}/GradleVerionsParams.groovy (93%) rename core/src/test/groovy/com/novoda/gradle/{release => }/test/TestProjectRule.groovy (97%) diff --git a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy index 2757814..3d83466 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy @@ -1,6 +1,7 @@ package com.novoda.gradle.release -import com.novoda.gradle.release.test.TestProjectRule +import com.novoda.gradle.test.GradleVerionsParams +import com.novoda.gradle.test.TestProjectRule import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.junit.Rule diff --git a/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy index 58107b4..3ab6495 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy @@ -1,6 +1,6 @@ package com.novoda.gradle.release -import com.novoda.gradle.release.test.TestProjectRule +import com.novoda.gradle.test.TestProjectRule import com.novoda.gradle.truth.GradleTruth import org.gradle.testkit.runner.TaskOutcome import org.junit.Rule diff --git a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy index a93be34..5800ee9 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy @@ -1,7 +1,7 @@ package com.novoda.gradle.release -import com.novoda.gradle.release.test.GradleScriptTemplates -import com.novoda.gradle.release.test.TestProjectRule +import com.novoda.gradle.test.GradleScriptTemplates +import com.novoda.gradle.test.TestProjectRule import com.novoda.gradle.truth.GradleTruth import org.junit.Rule import org.junit.Test diff --git a/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy index 9cf7b95..604d67b 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy @@ -1,6 +1,6 @@ package com.novoda.gradle.release -import com.novoda.gradle.release.test.TestProjectRule +import com.novoda.gradle.test.TestProjectRule import com.novoda.gradle.truth.GradleTruth import org.junit.Rule import org.junit.Test diff --git a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy index 7610cb5..7b48054 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy +++ b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy @@ -1,6 +1,7 @@ package com.novoda.gradle.release -import com.novoda.gradle.release.test.TestProjectRule +import com.novoda.gradle.test.GradleVerionsParams +import com.novoda.gradle.test.TestProjectRule import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.junit.Rule diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy b/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy similarity index 97% rename from core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy rename to core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy index 2ff71d7..0372ab3 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/BuildFolderRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy @@ -1,4 +1,4 @@ -package com.novoda.gradle.release.test +package com.novoda.gradle.test import org.junit.rules.TestRule diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy b/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy similarity index 98% rename from core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy rename to core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy index a2d3212..a8f27a2 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/GradleScriptTemplates.groovy +++ b/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy @@ -1,4 +1,4 @@ -package com.novoda.gradle.release.test +package com.novoda.gradle.test class GradleScriptTemplates { diff --git a/core/src/test/groovy/com/novoda/gradle/release/GradleVerionsParams.groovy b/core/src/test/groovy/com/novoda/gradle/test/GradleVerionsParams.groovy similarity index 93% rename from core/src/test/groovy/com/novoda/gradle/release/GradleVerionsParams.groovy rename to core/src/test/groovy/com/novoda/gradle/test/GradleVerionsParams.groovy index fa56bb6..b165209 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/GradleVerionsParams.groovy +++ b/core/src/test/groovy/com/novoda/gradle/test/GradleVerionsParams.groovy @@ -1,4 +1,4 @@ -package com.novoda.gradle.release +package com.novoda.gradle.test import org.gradle.testkit.runner.TaskOutcome diff --git a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy b/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy similarity index 97% rename from core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy rename to core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy index 662d3b2..bf2e2e9 100644 --- a/core/src/test/groovy/com/novoda/gradle/release/test/TestProjectRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy @@ -1,6 +1,6 @@ -package com.novoda.gradle.release.test +package com.novoda.gradle.test + -import com.novoda.gradle.test.GradleBuildResult import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.UnexpectedBuildFailure import org.junit.rules.TestRule From 3eae76b4806a7c21abee2c755a6d6c5afb20f4ee Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 15:12:46 +0200 Subject: [PATCH 26/73] Re-write test checking compatibility with Gradle versions --- .../AndroidDifferentGradleVersions.groovy | 55 -------------- .../GradleVersionsCompatibilityTest.groovy | 75 +++++++++++++++++++ .../JavaDifferentGradleVersions.groovy | 52 ------------- .../gradle/test/GradleVerionsParams.groovy | 21 ------ .../novoda/gradle/test/TestProjectRule.groovy | 7 +- 5 files changed, 80 insertions(+), 130 deletions(-) delete mode 100644 core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy create mode 100644 core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy delete mode 100644 core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy delete mode 100644 core/src/test/groovy/com/novoda/gradle/test/GradleVerionsParams.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy deleted file mode 100644 index 3d83466..0000000 --- a/core/src/test/groovy/com/novoda/gradle/release/AndroidDifferentGradleVersions.groovy +++ /dev/null @@ -1,55 +0,0 @@ -package com.novoda.gradle.release - -import com.novoda.gradle.test.GradleVerionsParams -import com.novoda.gradle.test.TestProjectRule -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -import static com.google.common.truth.Truth.assertThat - -@RunWith(Parameterized.class) -class AndroidDifferentGradleVersions { - - @Rule - public TestProjectRule projectRule = TestProjectRule.newAndroidProject() - - @Parameterized.Parameters(name = "{index}: test Gradle version {0}") - static Collection gradleVersionExpectedOutcome() { - return [ - // Gradle 4.0 is not support by the Android Gradle Plugin 3.x - new GradleVerionsParams(gradleVersion: "4.0", expectedGradleBuildFailure: true), - new GradleVerionsParams(gradleVersion: "4.1", expectedTaskOutcome: TaskOutcome.SUCCESS), - new GradleVerionsParams(gradleVersion: "4.2", expectedTaskOutcome: TaskOutcome.SUCCESS), - new GradleVerionsParams(gradleVersion: "4.3", expectedTaskOutcome: TaskOutcome.SUCCESS), - new GradleVerionsParams(gradleVersion: "4.4", expectedTaskOutcome: TaskOutcome.SUCCESS), - // TODO: Failure on Gradle 4.5. is **not** expected. It's failing because of changes in - // the UsageContext in our AndroidLibrary class - new GradleVerionsParams(gradleVersion: "4.5", expectedGradleBuildFailure: true), - ] - } - - private GradleVerionsParams testParams - - AndroidDifferentGradleVersions(GradleVerionsParams testParams) { - this.testParams = testParams - } - - @Test - void givenGradleVersion_WhenProjectBuild_ShouldHaveExpectedOutcome() { - def runner = GradleRunner.create() - .withProjectDir(projectRule.projectDir) - .withArguments("build", "bintrayUpload", "-PbintrayKey=key", "-PbintrayUser=user") - .withPluginClasspath() - .withGradleVersion(testParams.gradleVersion) - if (testParams.expectedGradleBuildFailure) { - runner.buildAndFail() - } else { - assertThat(runner.build().task(":bintrayUpload").outcome).isEqualTo(testParams.expectedTaskOutcome) - } - } - -} diff --git a/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy b/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy new file mode 100644 index 0000000..e623cb7 --- /dev/null +++ b/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy @@ -0,0 +1,75 @@ +package com.novoda.gradle.release + + +import com.novoda.gradle.test.TestProjectRule +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +import static com.google.common.truth.Truth.assertThat + +@RunWith(Parameterized.class) +class GradleVersionsCompatibilityTest { + + @Parameterized.Parameters(name = "{index}: {0}") + static Collection configurations() { + return [ + BuildConfiguration.forAndroid('4.0', false), + BuildConfiguration.forAndroid('4.1', true), + BuildConfiguration.forAndroid('4.2', true), + BuildConfiguration.forAndroid('4.3', true), + BuildConfiguration.forAndroid('4.4', true), + BuildConfiguration.forAndroid('4.5', false), + BuildConfiguration.forJava('4.0', true), + BuildConfiguration.forJava('4.1', true), + BuildConfiguration.forJava('4.2', true), + BuildConfiguration.forJava('4.3', true), + BuildConfiguration.forJava('4.4', true), + BuildConfiguration.forJava('4.5', true) + ] + } + + @Rule + public final TestProjectRule testProject + private final BuildConfiguration configuration + + GradleVersionsCompatibilityTest(BuildConfiguration configuration) { + this.configuration = configuration + this.testProject = configuration.testProject + } + + @Test + void shouldMatchExpectedOutcome() { + def additionalConfig = { it.withGradleVersion(configuration.gradleVersion) } + + def result = testProject.execute(additionalConfig, "build", "bintrayUpload", "-PbintrayKey=key", "-PbintrayUser=user") + + assertThat(result.success).isEqualTo(configuration.expectedBuildSuccess) + } + + private static class BuildConfiguration { + final String gradleVersion + final TestProjectRule testProject + final boolean expectedBuildSuccess + + static BuildConfiguration forAndroid(String gradleVersion, boolean expectedBuildSuccess) { + return new BuildConfiguration(gradleVersion, TestProjectRule.newAndroidProject(), expectedBuildSuccess) + } + + static BuildConfiguration forJava(String gradleVersion, boolean expectedBuildSuccess) { + return new BuildConfiguration(gradleVersion, TestProjectRule.newJavaProject(), expectedBuildSuccess) + } + + private BuildConfiguration(String gradleVersion, TestProjectRule testProject, boolean expectedBuildSuccess) { + this.gradleVersion = gradleVersion + this.testProject = testProject + this.expectedBuildSuccess = expectedBuildSuccess + } + + @Override + String toString() { + return "Build ${testProject.projectType} project with Gradle version $gradleVersion" + } + } +} diff --git a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy b/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy deleted file mode 100644 index 7b48054..0000000 --- a/core/src/test/groovy/com/novoda/gradle/release/JavaDifferentGradleVersions.groovy +++ /dev/null @@ -1,52 +0,0 @@ -package com.novoda.gradle.release - -import com.novoda.gradle.test.GradleVerionsParams -import com.novoda.gradle.test.TestProjectRule -import org.gradle.testkit.runner.GradleRunner -import org.gradle.testkit.runner.TaskOutcome -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -import static com.google.common.truth.Truth.assertThat - -@RunWith(Parameterized.class) -class JavaDifferentGradleVersions { - - @Rule - public TestProjectRule projectRule = TestProjectRule.newJavaProject() - - @Parameterized.Parameters(name = "{index}: test Gradle version {0}") - static Collection gradleVersionExpectedOutcome() { - return [ - new GradleVerionsParams(gradleVersion: "4.0", expectedTaskOutcome: TaskOutcome.SUCCESS), - new GradleVerionsParams(gradleVersion: "4.1", expectedTaskOutcome: TaskOutcome.SUCCESS), - new GradleVerionsParams(gradleVersion: "4.2", expectedTaskOutcome: TaskOutcome.SUCCESS), - new GradleVerionsParams(gradleVersion: "4.3", expectedTaskOutcome: TaskOutcome.SUCCESS), - new GradleVerionsParams(gradleVersion: "4.4", expectedTaskOutcome: TaskOutcome.SUCCESS), - new GradleVerionsParams(gradleVersion: "4.5", expectedTaskOutcome: TaskOutcome.SUCCESS), - ] - } - - private GradleVerionsParams testParams - - JavaDifferentGradleVersions(GradleVerionsParams testParams) { - this.testParams = testParams - } - - @Test - void givenGradleVersion_WhenProjectBuild_ShouldHaveExpectedOutcome() { - def runner = GradleRunner.create() - .withProjectDir(projectRule.projectDir) - .withArguments("build", "bintrayUpload", "-PbintrayKey=key", "-PbintrayUser=user") - .withPluginClasspath() - .withGradleVersion(testParams.gradleVersion) - if (testParams.expectedGradleBuildFailure) { - runner.buildAndFail() - } else { - assertThat(runner.build().task(":bintrayUpload").outcome).isEqualTo(testParams.expectedTaskOutcome) - } - } - -} diff --git a/core/src/test/groovy/com/novoda/gradle/test/GradleVerionsParams.groovy b/core/src/test/groovy/com/novoda/gradle/test/GradleVerionsParams.groovy deleted file mode 100644 index b165209..0000000 --- a/core/src/test/groovy/com/novoda/gradle/test/GradleVerionsParams.groovy +++ /dev/null @@ -1,21 +0,0 @@ -package com.novoda.gradle.test - -import org.gradle.testkit.runner.TaskOutcome - -class GradleVerionsParams { - - String gradleVersion = "0" - - boolean expectedGradleBuildFailure = false - - TaskOutcome expectedTaskOutcome = TaskOutcome.FAILED - - @Override - String toString() { - return "GradleVerionsParams{" + - "gradleVersion='" + gradleVersion + '\'' + - ", expectedGradleBuildFailure=" + expectedGradleBuildFailure + - ", expectedTaskOutcome=" + expectedTaskOutcome + - '}' - } -} diff --git a/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy b/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy index bf2e2e9..7dbaa7f 100644 --- a/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy +++ b/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy @@ -1,6 +1,7 @@ package com.novoda.gradle.test +import org.gradle.api.Action import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.UnexpectedBuildFailure import org.junit.rules.TestRule @@ -82,12 +83,14 @@ class TestProjectRule implements TestRule { return project.name().toLowerCase() } - GradleBuildResult execute(String... arguments) { + GradleBuildResult execute(Action additionalConfig = {}, String... arguments) { def runner = GradleRunner.create() .forwardOutput() .withPluginClasspath() .withProjectDir(projectDir) - .withArguments(arguments) + additionalConfig.execute(runner) + runner.withArguments(arguments) + try { return new GradleBuildResult(runner.build(), true) } catch (UnexpectedBuildFailure e) { From 13ecae98ac4779ec277359d72a51861cd5158e9f Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 17:12:53 +0200 Subject: [PATCH 27/73] Move core module inside plugin/ folder --- {core => plugin}/README.md | 2 +- {core => plugin/core}/build.gradle | 0 .../groovy/com/novoda/gradle/release/AndroidArtifacts.groovy | 0 .../groovy/com/novoda/gradle/release/AndroidLibrary.groovy | 0 .../main/groovy/com/novoda/gradle/release/Artifacts.groovy | 0 .../com/novoda/gradle/release/BintrayConfiguration.groovy | 0 .../novoda/gradle/release/GradlePluginPropertyFinder.groovy | 0 .../groovy/com/novoda/gradle/release/JavaArtifacts.groovy | 0 .../groovy/com/novoda/gradle/release/PropertyFinder.groovy | 0 .../groovy/com/novoda/gradle/release/PublishExtension.groovy | 0 .../groovy/com/novoda/gradle/release/ReleasePlugin.groovy | 0 .../groovy/com/novoda/gradle/release/BintrayUploadTest.groovy | 0 .../release/GeneratePomFileForMavenPublicationTest.groovy | 0 .../gradle/release/GradleVersionsCompatibilityTest.groovy | 0 .../novoda/gradle/release/InvalidExtensionSetupTest.groovy | 0 .../test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy | 2 +- .../groovy/com/novoda/gradle/test/GradleBuildResult.groovy | 0 .../com/novoda/gradle/test/GradleScriptTemplates.groovy | 0 .../test/groovy/com/novoda/gradle/test/TestProjectRule.groovy | 0 .../groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy | 0 .../com/novoda/gradle/truth/GradleBuildResultSubject.groovy | 0 .../test/groovy/com/novoda/gradle/truth/GradleTruth.groovy | 0 settings.gradle | 4 +++- 23 files changed, 5 insertions(+), 3 deletions(-) rename {core => plugin}/README.md (62%) rename {core => plugin/core}/build.gradle (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/BintrayConfiguration.groovy (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/GradlePluginPropertyFinder.groovy (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/PropertyFinder.groovy (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/PublishExtension.groovy (100%) rename {core => plugin/core}/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy (98%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy (100%) rename {core => plugin/core}/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy (100%) diff --git a/core/README.md b/plugin/README.md similarity index 62% rename from core/README.md rename to plugin/README.md index 340abbc..7fb48b3 100644 --- a/core/README.md +++ b/plugin/README.md @@ -1,6 +1,6 @@ This is the module for the source code of the `bintray-release` plugin. -The entry point for this code is the [ReleasePlugin.groovy](src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy) class. +The entry point for this code is the [ReleasePlugin.groovy](core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy) class. If this is your first time, you'll probably want to start there. If you want to test your code changes you make here, see the [samples module](../samples/) diff --git a/core/build.gradle b/plugin/core/build.gradle similarity index 100% rename from core/build.gradle rename to plugin/core/build.gradle diff --git a/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy diff --git a/core/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy diff --git a/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy diff --git a/core/src/main/groovy/com/novoda/gradle/release/BintrayConfiguration.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/BintrayConfiguration.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/BintrayConfiguration.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/BintrayConfiguration.groovy diff --git a/core/src/main/groovy/com/novoda/gradle/release/GradlePluginPropertyFinder.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/GradlePluginPropertyFinder.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/GradlePluginPropertyFinder.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/GradlePluginPropertyFinder.groovy diff --git a/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy diff --git a/core/src/main/groovy/com/novoda/gradle/release/PropertyFinder.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/PropertyFinder.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/PropertyFinder.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/PropertyFinder.groovy diff --git a/core/src/main/groovy/com/novoda/gradle/release/PublishExtension.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/PublishExtension.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/PublishExtension.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/PublishExtension.groovy diff --git a/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy similarity index 100% rename from core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy similarity index 98% rename from core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy index 0372ab3..d18c2fa 100644 --- a/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy @@ -57,7 +57,7 @@ class BuildFolderRule implements TestRule { void evaluate() throws Throwable { rootDir.mkdirs() base.evaluate() - rootDir.delete() + rootDir.deleteDir() } } } diff --git a/core/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/test/GradleBuildResult.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy diff --git a/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy similarity index 100% rename from core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy diff --git a/settings.gradle b/settings.gradle index 1820e64..c4dd713 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,4 @@ +rootProject.name = 'bintray-release' + include ':core' -rootProject.name = 'bintray-release' \ No newline at end of file +project(':core').projectDir = file('plugin/core') From c73fb871324eee2cff7bbdf96524e12138d0f30a Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 17:47:55 +0200 Subject: [PATCH 28/73] Use composite build in samples project to compile plugin locally --- samples/README.md | 15 ++++++++------- samples/android/build.gradle | 14 ++------------ samples/build.gradle | 10 ++++++++++ samples/buildSrc/build.gradle | 21 --------------------- samples/jvm/build.gradle | 2 +- samples/settings.gradle | 9 ++++++++- 6 files changed, 29 insertions(+), 42 deletions(-) delete mode 100644 samples/buildSrc/build.gradle diff --git a/samples/README.md b/samples/README.md index 4735dd7..6687131 100644 --- a/samples/README.md +++ b/samples/README.md @@ -1,13 +1,14 @@ -This ia a separate gradle project that is kept in the same VCS repository. +This is a separate gradle project that is kept in the same VCS repository. If you want to checkout/use/test the samples - you should import this folder as a separate project. -It is a separate project because with gradle plugins - you cannot have a reference to your working code as a module. -i.e. to test any changes you either have to make a release and depend on it, or like we are doing here have a second project +It is a separate project because gradle plugins need to be included as buildscript dependencies of the project using +them, and this requires the use of a compiled artifact of the plugin. -In the `buildSrc` folder, we have a folder reference to the source of this plugin (`../../core/src/main/groovy`). -This allows all modules in this folder to have the bintray-release plugin on their classpath at development time. - -(For more about `buildSrc` [read this](https://zeroturnaround.com/rebellabs/using-buildsrc-for-custom-logic-in-gradle-builds/)) +In order to streamline this process and avoid to play with manual local/remote releases we are making the `samples` +project to compile its own local version of the plugin directly from its sources, leveraging the Gradle composite builds support. +This allow us to instruct the `samples` project to resolve the `com.novoda:bintray-release:*` dependency using the output +of the plugin Gradle project. +(For more info about composite builds [read this](https://docs.gradle.org/current/userguide/composite_builds.html)) ### Modules diff --git a/samples/android/build.gradle b/samples/android/build.gradle index d361947..d71c1ed 100644 --- a/samples/android/build.gradle +++ b/samples/android/build.gradle @@ -1,15 +1,5 @@ -buildscript { - repositories { - google() - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:3.0.1' - } -} - apply plugin: 'com.android.library' -apply plugin: com.novoda.gradle.release.ReleasePlugin +apply plugin: 'com.novoda.bintray-release' android { compileSdkVersion 27 @@ -37,4 +27,4 @@ publish { publishVersion = '0.0.1' desc = 'Just a simple android lib sample' website = 'https://github.com/novoda/bintray-release' -} \ No newline at end of file +} diff --git a/samples/build.gradle b/samples/build.gradle index e69de29..d2e8bb2 100644 --- a/samples/build.gradle +++ b/samples/build.gradle @@ -0,0 +1,10 @@ +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:3.0.1' + classpath 'com.novoda:bintray-release:local' // version of the plugin compiled using composite builds + } +} diff --git a/samples/buildSrc/build.gradle b/samples/buildSrc/build.gradle deleted file mode 100644 index e7adbce..0000000 --- a/samples/buildSrc/build.gradle +++ /dev/null @@ -1,21 +0,0 @@ -plugins { - id 'groovy' -} - -repositories { - jcenter() -} - -// We have to make sure that we are using the same dependencies as in our 'core' project -dependencies { - compile gradleApi() - compile 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' -} - -sourceSets { - main { - groovy { - srcDir '../../core/src/main/groovy' - } - } -} \ No newline at end of file diff --git a/samples/jvm/build.gradle b/samples/jvm/build.gradle index 0e3175e..131e904 100644 --- a/samples/jvm/build.gradle +++ b/samples/jvm/build.gradle @@ -1,5 +1,5 @@ apply plugin: 'java-library' -apply plugin: com.novoda.gradle.release.ReleasePlugin +apply plugin: 'com.novoda.bintray-release' repositories { jcenter() diff --git a/samples/settings.gradle b/samples/settings.gradle index 7d88d48..98daf20 100644 --- a/samples/settings.gradle +++ b/samples/settings.gradle @@ -1,2 +1,9 @@ +rootProject.name = 'bintray-release-samples' + include ':jvm', ':android' -rootProject.name = 'bintray-release-samples' \ No newline at end of file + +includeBuild('../') { + dependencySubstitution { + substitute module('com.novoda:bintray-release:local') with project(':core') + } +} From 2515407adf99bd0a39d94c995608a996d426c935 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 21 Oct 2018 18:16:11 +0200 Subject: [PATCH 29/73] Extract publishing script to its own file --- plugin/core/build.gradle | 52 ++------------------------------------ plugin/core/publish.gradle | 50 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 50 deletions(-) create mode 100644 plugin/core/publish.gradle diff --git a/plugin/core/build.gradle b/plugin/core/build.gradle index 438eb2b..ee51161 100644 --- a/plugin/core/build.gradle +++ b/plugin/core/build.gradle @@ -8,37 +8,10 @@ plugins { version = '0.8.1' apply plugin: 'com.novoda.bintray-release' - -buildProperties { - cli { - using(project) - } - - bintray { - def bintrayCredentials = { - return isDryRun() ? - ['bintrayRepo': 'n/a', 'bintrayUser': 'n/a', 'bintrayKey': 'n/a'] : - new File("${System.getenv('BINTRAY_PROPERTIES')}") - } - using(bintrayCredentials()).or(cli) - description = '''This should contain the following properties: - | - bintrayRepo: name of the repo of the organisation to deploy the artifacts to - | - bintrayUser: name of the account used to deploy the artifacts - | - bintrayKey: API key of the account used to deploy the artifacts - '''.stripMargin() - } - - publish { - def generateVersion = { - return isSnapshot() ? "SNAPSHOT-${System.getenv('BUILD_NUMBER') ?: 'LOCAL'}" : project.version - } - using(['version': "${generateVersion()}"]).or(buildProperties.bintray) - } -} +apply from: 'publish.gradle' dependencies { - compile localGroovy() - compile 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' + implementation 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' testCompile 'junit:junit:4.12' testCompile 'com.google.truth:truth:0.42' @@ -63,24 +36,3 @@ gradlePlugin { } } } - -publish { - userOrg = 'novoda' - groupId = 'com.novoda' - artifactId = rootProject.name - desc = 'Super duper easy way to release your Android and other artifacts to bintray' - website = "https://github.com/novoda/${rootProject.name}" - - version = project.buildProperties.publish['version'].string - publishVersion = project.buildProperties.publish['version'].string - bintrayUser = project.buildProperties.publish['bintrayUser'].string - bintrayKey = project.buildProperties.publish['bintrayKey'].string - repoName = buildProperties.publish['bintrayRepo'].string -} - -boolean isDryRun() { - buildProperties.cli['dryRun'].or(true).boolean -} -boolean isSnapshot() { - buildProperties.cli['bintraySnapshot'].or(false).boolean -} diff --git a/plugin/core/publish.gradle b/plugin/core/publish.gradle new file mode 100644 index 0000000..63b40b1 --- /dev/null +++ b/plugin/core/publish.gradle @@ -0,0 +1,50 @@ +buildProperties { + cli { + using(project) + } + + bintray { + using(bintrayCredentials()).or(cli) + description = '''This should contain the following properties: + - bintrayRepo: name of the repo of the organisation to deploy the artifacts to + - bintrayUser: name of the account used to deploy the artifacts + - bintrayKey: API key of the account used to deploy the artifacts + '''.stripIndent() + } + + publish { + using(['version': "${generateVersion()}"]).or(buildProperties.bintray) + } +} + +publish { + userOrg = 'novoda' + groupId = 'com.novoda' + artifactId = rootProject.name + desc = 'Super duper easy way to release your Android and other artifacts to bintray' + website = "https://github.com/novoda/${rootProject.name}" + + version = project.buildProperties.publish['version'].string + publishVersion = project.buildProperties.publish['version'].string + bintrayUser = project.buildProperties.publish['bintrayUser'].string + bintrayKey = project.buildProperties.publish['bintrayKey'].string + repoName = buildProperties.publish['bintrayRepo'].string +} + +def bintrayCredentials() { + return isDryRun() ? + ['bintrayRepo': 'n/a', 'bintrayUser': 'n/a', 'bintrayKey': 'n/a'] : + new File("${System.getenv('BINTRAY_PROPERTIES')}") +} + +def generateVersion() { + return isSnapshot() ? "SNAPSHOT-${System.getenv('BUILD_NUMBER') ?: 'LOCAL'}" : project.version +} + +boolean isDryRun() { + buildProperties.cli['dryRun'].or(true).boolean +} + +boolean isSnapshot() { + buildProperties.cli['bintraySnapshot'].or(false).boolean +} From 22155a76497efab29750395c90b7fd1457c2f5c3 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 14:58:21 +0200 Subject: [PATCH 30/73] Avoid to delete test project build dir even if build successful --- .../test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy index d18c2fa..d7e310c 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy @@ -57,7 +57,6 @@ class BuildFolderRule implements TestRule { void evaluate() throws Throwable { rootDir.mkdirs() base.evaluate() - rootDir.deleteDir() } } } From fbec26ec9ae9e761c1269954a18292ba20de5387 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 14:59:13 +0200 Subject: [PATCH 31/73] Inject additomal GradleRunner configuratio in TestProjectRule via ctor --- .../GradleVersionsCompatibilityTest.groovy | 15 +++++++----- .../novoda/gradle/test/TestProjectRule.groovy | 24 ++++++++++++------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy index e623cb7..70c0ad9 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy @@ -1,7 +1,8 @@ package com.novoda.gradle.release - +import com.novoda.gradle.test.GradleScriptTemplates import com.novoda.gradle.test.TestProjectRule +import org.gradle.testkit.runner.GradleRunner import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -41,9 +42,7 @@ class GradleVersionsCompatibilityTest { @Test void shouldMatchExpectedOutcome() { - def additionalConfig = { it.withGradleVersion(configuration.gradleVersion) } - - def result = testProject.execute(additionalConfig, "build", "bintrayUpload", "-PbintrayKey=key", "-PbintrayUser=user") + def result = testProject.execute("build", "bintrayUpload", "-PbintrayKey=key", "-PbintrayUser=user") assertThat(result.success).isEqualTo(configuration.expectedBuildSuccess) } @@ -54,11 +53,15 @@ class GradleVersionsCompatibilityTest { final boolean expectedBuildSuccess static BuildConfiguration forAndroid(String gradleVersion, boolean expectedBuildSuccess) { - return new BuildConfiguration(gradleVersion, TestProjectRule.newAndroidProject(), expectedBuildSuccess) + def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } + def projectRule = TestProjectRule.newAndroidProject(GradleScriptTemplates.forAndroidProject(), additionalRunnerConfig) + return new BuildConfiguration(gradleVersion, projectRule, expectedBuildSuccess) } static BuildConfiguration forJava(String gradleVersion, boolean expectedBuildSuccess) { - return new BuildConfiguration(gradleVersion, TestProjectRule.newJavaProject(), expectedBuildSuccess) + def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } + def projectRule = TestProjectRule.newJavaProject(GradleScriptTemplates.forJavaProject(), additionalRunnerConfig) + return new BuildConfiguration(gradleVersion, projectRule, expectedBuildSuccess) } private BuildConfiguration(String gradleVersion, TestProjectRule testProject, boolean expectedBuildSuccess) { diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy index 7dbaa7f..c1a13ad 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy @@ -10,6 +10,8 @@ import org.junit.runners.model.Statement class TestProjectRule implements TestRule { + private static final Action NONE = {} + private enum ProjectType { JAVA, ANDROID } @@ -17,23 +19,25 @@ class TestProjectRule implements TestRule { private final ProjectType project private BuildFolderRule tempFolder private String buildScript + private Action additionalRunnerConfig - static TestProjectRule newJavaProject(String buildScript = GradleScriptTemplates.forJavaProject()) { - return new TestProjectRule(ProjectType.JAVA, buildScript) + static TestProjectRule newJavaProject(String buildScript = GradleScriptTemplates.forJavaProject(), Action additionalRunnerConfig = NONE) { + return new TestProjectRule(ProjectType.JAVA, buildScript, additionalRunnerConfig) } - static TestProjectRule newAndroidProject(String buildScript = GradleScriptTemplates.forAndroidProject()) { - return new TestProjectRule(ProjectType.ANDROID, buildScript) + static TestProjectRule newAndroidProject(String buildScript = GradleScriptTemplates.forAndroidProject(), Action additionalConfig = NONE) { + return new TestProjectRule(ProjectType.ANDROID, buildScript, additionalConfig) } - private TestProjectRule(ProjectType project, String buildScript) { + private TestProjectRule(ProjectType project, String buildScript, Action additionalRunnerConfig) { this.project = project this.buildScript = buildScript + this.additionalRunnerConfig = additionalRunnerConfig } @Override Statement apply(Statement base, Description description) { - tempFolder = new BuildFolderRule("test-projects/${description.testClass.canonicalName}/${description.methodName}") + tempFolder = new BuildFolderRule("test-projects/${description.testClass.canonicalName}/${description.methodName ?: ''}") def statement = new Statement() { @Override void evaluate() throws Throwable { @@ -83,12 +87,12 @@ class TestProjectRule implements TestRule { return project.name().toLowerCase() } - GradleBuildResult execute(Action additionalConfig = {}, String... arguments) { + GradleBuildResult execute(String... arguments) { def runner = GradleRunner.create() .forwardOutput() .withPluginClasspath() .withProjectDir(projectDir) - additionalConfig.execute(runner) + additionalRunnerConfig.execute(runner) runner.withArguments(arguments) try { @@ -97,4 +101,8 @@ class TestProjectRule implements TestRule { return new GradleBuildResult(e.buildResult, false) } } + + File buildDir() { + return new File(projectDir, 'build') + } } From b812c80edca298d52e93722bab3f0119acd2942f Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 18:35:55 +0200 Subject: [PATCH 32/73] Avoid use of illegal chars in test project name --- .../gradle/release/GradleVersionsCompatibilityTest.groovy | 4 ++-- .../test/groovy/com/novoda/gradle/test/TestProjectRule.groovy | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy index 70c0ad9..ad08bb9 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy @@ -13,7 +13,7 @@ import static com.google.common.truth.Truth.assertThat @RunWith(Parameterized.class) class GradleVersionsCompatibilityTest { - @Parameterized.Parameters(name = "{index}: {0}") + @Parameterized.Parameters(name = "{0}") static Collection configurations() { return [ BuildConfiguration.forAndroid('4.0', false), @@ -72,7 +72,7 @@ class GradleVersionsCompatibilityTest { @Override String toString() { - return "Build ${testProject.projectType} project with Gradle version $gradleVersion" + return "${testProject.projectType.capitalize()} project with Gradle $gradleVersion" } } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy index c1a13ad..b6fe800 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy @@ -37,7 +37,8 @@ class TestProjectRule implements TestRule { @Override Statement apply(Statement base, Description description) { - tempFolder = new BuildFolderRule("test-projects/${description.testClass.canonicalName}/${description.methodName ?: ''}") + def methodName = (description.methodName ? "/$description.methodName": '') + tempFolder = new BuildFolderRule("test-projects/${description.testClass.canonicalName}${methodName}/test") def statement = new Statement() { @Override void evaluate() throws Throwable { From 6a8b02fa5a1cf5e3985b4b6c4e11e68f1a5b1555 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 18:36:22 +0200 Subject: [PATCH 33/73] Expose file utils via TestProjectRule --- .../novoda/gradle/test/TestProjectRule.groovy | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy index b6fe800..e1c4e64 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy @@ -2,6 +2,10 @@ package com.novoda.gradle.test import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.FileTree +import org.gradle.testfixtures.ProjectBuilder import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.UnexpectedBuildFailure import org.junit.rules.TestRule @@ -17,6 +21,7 @@ class TestProjectRule implements TestRule { } private final ProjectType project + private final Project utils = ProjectBuilder.newInstance().build() private BuildFolderRule tempFolder private String buildScript private Action additionalRunnerConfig @@ -106,4 +111,16 @@ class TestProjectRule implements TestRule { File buildDir() { return new File(projectDir, 'build') } + + File buildFile(String path) { + return new File(buildDir(), path) + } + + ConfigurableFileTree fileTree(String baseDir) { + return utils.fileTree(new File(projectDir, baseDir)) + } + + FileTree zipTree(String zipPath) { + return utils.zipTree(new File(projectDir, zipPath)) + } } From 3d3774e098c0d39674af8f7d3cef9c68dbb74c31 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 18:37:26 +0200 Subject: [PATCH 34/73] Add more tests around uploading of artifacts for Java projects --- .../gradle/release/JavaArtifacts.groovy | 5 +- .../release/BintrayUploadJavaTest.groovy | 104 ++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy index 3376bcd..c51ad88 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy @@ -1,7 +1,10 @@ package com.novoda.gradle.release import org.gradle.api.Project +import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.api.tasks.javadoc.Javadoc class JavaArtifacts implements Artifacts { @@ -19,7 +22,7 @@ class JavaArtifacts implements Artifacts { def javadocJar(String publicationName, Project project) { project.task(publicationName + 'JavadocJar', type: Jar) { classifier = 'javadoc' - from project.javadoc.destinationDir + from project.files(project.javadoc) } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy new file mode 100644 index 0000000..1d482da --- /dev/null +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy @@ -0,0 +1,104 @@ +package com.novoda.gradle.release + + +import com.novoda.gradle.test.GradleBuildResult +import com.novoda.gradle.test.TestProjectRule +import com.novoda.gradle.truth.GradleTruth +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.FileTree +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Before +import org.junit.ClassRule +import org.junit.Test + +import static com.google.common.truth.Truth.assertThat + +class BintrayUploadJavaTest { + + private static final String BASE_UPLOAD_PATH = 'https://api.bintray.com/content/novoda/maven/test/1.0/com/novoda/test/1.0/test-1.0' + private static final String SOURCES_UPLOAD_PATH = "$BASE_UPLOAD_PATH-sources.jar" + private static final String JAVADOC_UPLOAD_PATH = "$BASE_UPLOAD_PATH-javadoc.jar" + private static final String POM_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.pom" + private static final String JAR_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.jar" + + @ClassRule + public static TestProjectRule testProject = TestProjectRule.newJavaProject() + private static GradleBuildResult result + + @Before + void setUp() { + if (result == null) { + result = testProject.execute('clean', 'build', ":bintrayUpload", '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') + } + } + + @Test + void shouldBuildLibrary() { + GradleTruth.assertThat(result.task(':build')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldGeneratePomFile() { + GradleTruth.assertThat(result.task(':generatePomFileForMavenPublication')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldGenerateJavadocs() { + GradleTruth.assertThat(result.task(':javadoc')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldPackageAllGeneratedJavadocs() { + GradleTruth.assertThat(result.task(':mavenJavadocJar')).hasOutcome(TaskOutcome.SUCCESS) + + ConfigurableFileTree generatedFiles = testProject.fileTree('build/docs/javadoc') + List includePatterns = generatedFiles.collect { '**' + it.path - generatedFiles.dir.path } + FileTree jarfile = testProject.zipTree('build/libs/test-javadoc.jar') + assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) + } + + @Test + void shouldPackageAllSources() { + GradleTruth.assertThat(result.task(':mavenSourcesJar')).hasOutcome(TaskOutcome.SUCCESS) + + ConfigurableFileTree sourceFiles = testProject.fileTree('src/main/java') + List includePatterns = sourceFiles.collect { '**' + it.path - sourceFiles.dir.path } + FileTree jarfile = testProject.zipTree('build/libs/test-sources.jar') + assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) + } + + @Test + void shouldPublishToMavenLocal() { + GradleTruth.assertThat(result.task(':publishMavenPublicationToMavenLocal')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldRunUploadTask() { + GradleTruth.assertThat(result.task(":bintrayUpload")).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldUploadSourcesJar() { + assertThat(result.output).contains(SOURCES_UPLOAD_PATH) + } + + @Test + void shouldUploadJavadocJar() { + assertThat(result.output).contains(JAVADOC_UPLOAD_PATH) + } + + @Test + void shouldUploadPomFile() { + assertThat(result.output).contains(POM_UPLOAD_PATH) + } + + @Test + void shouldUploadLibraryJar() { + assertThat(result.output).contains(JAR_UPLOAD_PATH) + } + + @Test + void shouldBuildSuccessfully() { + GradleTruth.assertThat(result).isSuccess() + } +} From 6d66e4a073eefac0731d888a563620ef01eb12bb Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 18:46:33 +0200 Subject: [PATCH 35/73] Add Android-specific test around upload task --- .../release/BintrayUploadAndroidTest.groovy | 104 ++++++++++++++++++ .../gradle/release/BintrayUploadTest.groovy | 56 ---------- 2 files changed, 104 insertions(+), 56 deletions(-) create mode 100644 plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy delete mode 100644 plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy new file mode 100644 index 0000000..16cb737 --- /dev/null +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy @@ -0,0 +1,104 @@ +package com.novoda.gradle.release + + +import com.novoda.gradle.test.GradleBuildResult +import com.novoda.gradle.test.TestProjectRule +import com.novoda.gradle.truth.GradleTruth +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.FileTree +import org.gradle.testkit.runner.TaskOutcome +import org.junit.Before +import org.junit.ClassRule +import org.junit.Test + +import static com.google.common.truth.Truth.assertThat + +class BintrayUploadAndroidTest { + + private static final String BASE_UPLOAD_PATH = 'https://api.bintray.com/content/novoda/maven/test/1.0/com/novoda/test/1.0/test-1.0' + private static final String SOURCES_UPLOAD_PATH = "$BASE_UPLOAD_PATH-sources.jar" + private static final String JAVADOC_UPLOAD_PATH = "$BASE_UPLOAD_PATH-javadoc.jar" + private static final String POM_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.pom" + private static final String AAR_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.aar" + + @ClassRule + public static TestProjectRule testProject = TestProjectRule.newAndroidProject() + private static GradleBuildResult result + + @Before + void setUp() { + if (result == null) { + result = testProject.execute('clean', 'build', ":bintrayUpload", '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') + } + } + + @Test + void shouldBuildLibrary() { + GradleTruth.assertThat(result.task(':build')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldGeneratePomFile() { + GradleTruth.assertThat(result.task(':generatePomFileForReleasePublication')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldGenerateJavadocs() { + GradleTruth.assertThat(result.task(':releaseAndroidJavadocs')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldPackageAllGeneratedJavadocs() { + GradleTruth.assertThat(result.task(':releaseAndroidJavadocsJar')).hasOutcome(TaskOutcome.SUCCESS) + + ConfigurableFileTree generatedFiles = testProject.fileTree('build/docs/javadoc') + List includePatterns = generatedFiles.collect { '**' + it.path - generatedFiles.dir.path } + FileTree jarfile = testProject.zipTree('build/libs/test-javadoc.jar') + assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) + } + + @Test + void shouldPackageAllSources() { + GradleTruth.assertThat(result.task(':releaseAndroidSourcesJar')).hasOutcome(TaskOutcome.SUCCESS) + + ConfigurableFileTree sourceFiles = testProject.fileTree('src/main/java') + List includePatterns = sourceFiles.collect { '**' + it.path - sourceFiles.dir.path } + FileTree jarfile = testProject.zipTree('build/libs/test-sources.jar') + assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) + } + + @Test + void shouldPublishToMavenLocal() { + GradleTruth.assertThat(result.task(':publishReleasePublicationToMavenLocal')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldRunUploadTask() { + GradleTruth.assertThat(result.task(":bintrayUpload")).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldUploadSourcesJar() { + assertThat(result.output).contains(SOURCES_UPLOAD_PATH) + } + + @Test + void shouldUploadJavadocJar() { + assertThat(result.output).contains(JAVADOC_UPLOAD_PATH) + } + + @Test + void shouldUploadPomFile() { + assertThat(result.output).contains(POM_UPLOAD_PATH) + } + + @Test + void shouldUploadLibraryAar() { + assertThat(result.output).contains(AAR_UPLOAD_PATH) + } + + @Test + void shouldBuildSuccessfully() { + GradleTruth.assertThat(result).isSuccess() + } +} diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy deleted file mode 100644 index 3ab6495..0000000 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadTest.groovy +++ /dev/null @@ -1,56 +0,0 @@ -package com.novoda.gradle.release - -import com.novoda.gradle.test.TestProjectRule -import com.novoda.gradle.truth.GradleTruth -import org.gradle.testkit.runner.TaskOutcome -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -@RunWith(Parameterized.class) -class BintrayUploadTest { - - @Parameterized.Parameters(name = "{index}: test upload task for {0}") - static Collection gradleVersionExpectedOutcome() { - return [Parameter.forJavaProject(), Parameter.forAndroidProject()] - } - - @Rule - public final TestProjectRule testProject - - BintrayUploadTest(Parameter parameter) { - this.testProject = parameter.rule - } - - @Test - void shouldUploadSuccessfullyAsDryRun() { - def uploadTaskName = ":bintrayUpload" - - def result = testProject.execute('build', uploadTaskName, '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') - - GradleTruth.assertThat(result).isSuccess() - GradleTruth.assertThat(result.task(uploadTaskName)).hasOutcome(TaskOutcome.SUCCESS) - } - - private static class Parameter { - - static Parameter forJavaProject() { - return new Parameter(TestProjectRule.newJavaProject()) - } - - static Parameter forAndroidProject() { - return new Parameter(TestProjectRule.newAndroidProject()) - } - - final TestProjectRule rule - - Parameter(TestProjectRule rule) { - this.rule = rule - } - - String toString() { - return rule.projectType - } - } -} From 89dab9f626670c6be859380da7911ced3bef9a58 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 18:51:06 +0200 Subject: [PATCH 36/73] Remove unused imports --- .../main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy | 3 --- 1 file changed, 3 deletions(-) diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy index c51ad88..d358544 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy @@ -1,10 +1,7 @@ package com.novoda.gradle.release import org.gradle.api.Project -import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.bundling.Jar -import org.gradle.api.tasks.compile.JavaCompile -import org.gradle.api.tasks.javadoc.Javadoc class JavaArtifacts implements Artifacts { From 2066b622840ce65e0683e9347fc8d4be479c080d Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 18:53:51 +0200 Subject: [PATCH 37/73] Use implicit dependency based on task output instead of explicit dependsOn --- .../groovy/com/novoda/gradle/release/AndroidArtifacts.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy index f573560..b665fa3 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy @@ -35,9 +35,9 @@ class AndroidArtifacts implements Artifacts { classpath += variant.javaCompile.outputs.files } - project.task(variant.name + 'AndroidJavadocsJar', type: Jar, dependsOn: androidJavadocs) { + project.task(variant.name + 'AndroidJavadocsJar', type: Jar) { classifier = 'javadoc' - from androidJavadocs.destinationDir + from project.files(androidJavadocs) } } From 5a95eab9b9c07096ac2df19113b286d2936bc861 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 21:16:55 +0200 Subject: [PATCH 38/73] Avoid to have BuildFolder as TestRule --- ...ldFolderRule.groovy => BuildFolder.groovy} | 39 ++++++++----------- .../novoda/gradle/test/TestProjectRule.groovy | 9 ++--- 2 files changed, 21 insertions(+), 27 deletions(-) rename plugin/core/src/test/groovy/com/novoda/gradle/test/{BuildFolderRule.groovy => BuildFolder.groovy} (61%) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolder.groovy similarity index 61% rename from plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolder.groovy index d7e310c..6cebf5e 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolderRule.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/BuildFolder.groovy @@ -1,27 +1,29 @@ package com.novoda.gradle.test +class BuildFolder { -import org.junit.rules.TestRule -import org.junit.runner.Description -import org.junit.runners.model.Statement + private Closure rootDirProvider -class BuildFolderRule implements TestRule { - - private File rootDir - - BuildFolderRule(String path = '') { + BuildFolder(String path = '') { def start = new File(getResource('.').file) if (start.path.endsWith('build/classes/groovy/test')) { - rootDir = new File(start.parentFile.parentFile.parentFile, path) + rootDir(new File(start.parentFile.parentFile.parentFile, path)) } else if (start.path.endsWith('out/test/classes')) { - rootDir = new File(start.parentFile.parentFile.parentFile, "build/$path") + rootDir(new File(start.parentFile.parentFile.parentFile, "build/$path")) } else { throw new UnsupportedOperationException("Unable to identify build folder from path: $start") } } + private void rootDir(File file) { + rootDirProvider = { + file.mkdirs() + return file + }.memoize() + } + private static URL getResource(String resourceName) { - ClassLoader loader = Thread.currentThread().getContextClassLoader() ?: BuildFolderRule.class.getClassLoader() + ClassLoader loader = Thread.currentThread().getContextClassLoader() ?: BuildFolder.class.getClassLoader() URL url = loader.getResource(resourceName) if (url == null) { throw new IllegalArgumentException("resource ${resourceName} not found.") @@ -29,6 +31,10 @@ class BuildFolderRule implements TestRule { return url } + private File getRootDir() { + rootDirProvider.call() + } + File newFolder(String path) { File folder = new File(rootDir, path) folder.mkdirs() @@ -49,15 +55,4 @@ class BuildFolderRule implements TestRule { File getRoot() { return rootDir } - - @Override - Statement apply(Statement base, Description description) { - return new Statement() { - @Override - void evaluate() throws Throwable { - rootDir.mkdirs() - base.evaluate() - } - } - } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy index e1c4e64..f7cb1d8 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy @@ -22,7 +22,7 @@ class TestProjectRule implements TestRule { private final ProjectType project private final Project utils = ProjectBuilder.newInstance().build() - private BuildFolderRule tempFolder + private BuildFolder tempFolder private String buildScript private Action additionalRunnerConfig @@ -42,9 +42,9 @@ class TestProjectRule implements TestRule { @Override Statement apply(Statement base, Description description) { - def methodName = (description.methodName ? "/$description.methodName": '') - tempFolder = new BuildFolderRule("test-projects/${description.testClass.canonicalName}${methodName}/test") - def statement = new Statement() { + def methodName = (description.methodName ? "/$description.methodName" : '') + tempFolder = new BuildFolder("test-projects/${description.testClass.canonicalName}${methodName}/test") + return new Statement() { @Override void evaluate() throws Throwable { createSourceCode() @@ -54,7 +54,6 @@ class TestProjectRule implements TestRule { base.evaluate() } } - return tempFolder.apply(statement, description) } File getProjectDir() { From 640a9f6ea9528d96a7cf96b5881e26df837a6fa2 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 21:24:58 +0200 Subject: [PATCH 39/73] Provide option to use TestProject not as testrule --- .../release/BintrayUploadAndroidTest.groovy | 4 +- .../release/BintrayUploadJavaTest.groovy | 11 +++-- ...eratePomFileForMavenPublicationTest.groovy | 18 ++++---- .../GradleVersionsCompatibilityTest.groovy | 16 +++---- .../release/InvalidExtensionSetupTest.groovy | 4 +- ...tProjectRule.groovy => TestProject.groovy} | 44 ++++++++++++------- 6 files changed, 54 insertions(+), 43 deletions(-) rename plugin/core/src/test/groovy/com/novoda/gradle/test/{TestProjectRule.groovy => TestProject.groovy} (62%) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy index 16cb737..ec31470 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy @@ -2,7 +2,7 @@ package com.novoda.gradle.release import com.novoda.gradle.test.GradleBuildResult -import com.novoda.gradle.test.TestProjectRule +import com.novoda.gradle.test.TestProject import com.novoda.gradle.truth.GradleTruth import org.gradle.api.file.ConfigurableFileTree import org.gradle.api.file.FileTree @@ -22,7 +22,7 @@ class BintrayUploadAndroidTest { private static final String AAR_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.aar" @ClassRule - public static TestProjectRule testProject = TestProjectRule.newAndroidProject() + public static TestProject testProject = TestProject.newAndroidProject() private static GradleBuildResult result @Before diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy index 1d482da..9336366 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy @@ -1,14 +1,12 @@ package com.novoda.gradle.release - import com.novoda.gradle.test.GradleBuildResult -import com.novoda.gradle.test.TestProjectRule +import com.novoda.gradle.test.TestProject import com.novoda.gradle.truth.GradleTruth import org.gradle.api.file.ConfigurableFileTree import org.gradle.api.file.FileTree import org.gradle.testkit.runner.TaskOutcome import org.junit.Before -import org.junit.ClassRule import org.junit.Test import static com.google.common.truth.Truth.assertThat @@ -21,13 +19,14 @@ class BintrayUploadJavaTest { private static final String POM_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.pom" private static final String JAR_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.jar" - @ClassRule - public static TestProjectRule testProject = TestProjectRule.newJavaProject() + private static TestProject testProject private static GradleBuildResult result @Before void setUp() { - if (result == null) { + if (testProject == null) { + testProject = TestProject.newJavaProject() + testProject.init("${this.class.canonicalName}/test") result = testProject.execute('clean', 'build', ":bintrayUpload", '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy index 5800ee9..38e80fb 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy @@ -1,7 +1,7 @@ package com.novoda.gradle.release import com.novoda.gradle.test.GradleScriptTemplates -import com.novoda.gradle.test.TestProjectRule +import com.novoda.gradle.test.TestProject import com.novoda.gradle.truth.GradleTruth import org.junit.Rule import org.junit.Test @@ -20,12 +20,12 @@ class GeneratePomFileForMavenPublicationTest { } @Rule - public final TestProjectRule testProject + public final TestProject testProject private final String publicationName GeneratePomFileForMavenPublicationTest(Parameter parameter) { - this.testProject = parameter.rule + this.testProject = parameter.testProject this.publicationName = parameter.publicationName } @@ -55,13 +55,13 @@ class GeneratePomFileForMavenPublicationTest { static Parameter forJavaProject() { return new Parameter( - TestProjectRule.newJavaProject(templateFrom(GradleScriptTemplates.forJavaProject())), + TestProject.newJavaProject(templateFrom(GradleScriptTemplates.forJavaProject())), 'maven') } static Parameter forAndroidProject() { return new Parameter( - TestProjectRule.newAndroidProject(templateFrom(GradleScriptTemplates.forAndroidProject())), + TestProject.newAndroidProject(templateFrom(GradleScriptTemplates.forAndroidProject())), 'release') } @@ -78,16 +78,16 @@ class GeneratePomFileForMavenPublicationTest { } - final TestProjectRule rule + final TestProject testProject final String publicationName - Parameter(TestProjectRule rule, String publicationName) { - this.rule = rule + Parameter(TestProject testProject, String publicationName) { + this.testProject = testProject this.publicationName = publicationName } String toString() { - return rule.projectType + return testProject.projectType } } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy index ad08bb9..864d2d5 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy @@ -1,7 +1,7 @@ package com.novoda.gradle.release import com.novoda.gradle.test.GradleScriptTemplates -import com.novoda.gradle.test.TestProjectRule +import com.novoda.gradle.test.TestProject import org.gradle.testkit.runner.GradleRunner import org.junit.Rule import org.junit.Test @@ -32,7 +32,7 @@ class GradleVersionsCompatibilityTest { } @Rule - public final TestProjectRule testProject + public final TestProject testProject private final BuildConfiguration configuration GradleVersionsCompatibilityTest(BuildConfiguration configuration) { @@ -49,22 +49,22 @@ class GradleVersionsCompatibilityTest { private static class BuildConfiguration { final String gradleVersion - final TestProjectRule testProject + final TestProject testProject final boolean expectedBuildSuccess static BuildConfiguration forAndroid(String gradleVersion, boolean expectedBuildSuccess) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } - def projectRule = TestProjectRule.newAndroidProject(GradleScriptTemplates.forAndroidProject(), additionalRunnerConfig) - return new BuildConfiguration(gradleVersion, projectRule, expectedBuildSuccess) + def testProject = TestProject.newAndroidProject(GradleScriptTemplates.forAndroidProject(), additionalRunnerConfig) + return new BuildConfiguration(gradleVersion, testProject, expectedBuildSuccess) } static BuildConfiguration forJava(String gradleVersion, boolean expectedBuildSuccess) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } - def projectRule = TestProjectRule.newJavaProject(GradleScriptTemplates.forJavaProject(), additionalRunnerConfig) - return new BuildConfiguration(gradleVersion, projectRule, expectedBuildSuccess) + def testProject = TestProject.newJavaProject(GradleScriptTemplates.forJavaProject(), additionalRunnerConfig) + return new BuildConfiguration(gradleVersion, testProject, expectedBuildSuccess) } - private BuildConfiguration(String gradleVersion, TestProjectRule testProject, boolean expectedBuildSuccess) { + private BuildConfiguration(String gradleVersion, TestProject testProject, boolean expectedBuildSuccess) { this.gradleVersion = gradleVersion this.testProject = testProject this.expectedBuildSuccess = expectedBuildSuccess diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy index 604d67b..9c6ee2b 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/InvalidExtensionSetupTest.groovy @@ -1,6 +1,6 @@ package com.novoda.gradle.release -import com.novoda.gradle.test.TestProjectRule +import com.novoda.gradle.test.TestProject import com.novoda.gradle.truth.GradleTruth import org.junit.Rule import org.junit.Test @@ -23,7 +23,7 @@ class InvalidExtensionSetupTest { """.stripIndent() @Rule - public TestProjectRule testProject = TestProjectRule.newJavaProject(buildScript) + public TestProject testProject = TestProject.newJavaProject(buildScript) @Test void shouldFailWhenMissingMandatoryAttributes() { diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProject.groovy similarity index 62% rename from plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/test/TestProject.groovy index f7cb1d8..05da5d1 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProjectRule.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/TestProject.groovy @@ -12,7 +12,9 @@ import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -class TestProjectRule implements TestRule { +import static com.google.common.base.Preconditions.checkNotNull + +class TestProject implements TestRule { private static final Action NONE = {} @@ -26,15 +28,15 @@ class TestProjectRule implements TestRule { private String buildScript private Action additionalRunnerConfig - static TestProjectRule newJavaProject(String buildScript = GradleScriptTemplates.forJavaProject(), Action additionalRunnerConfig = NONE) { - return new TestProjectRule(ProjectType.JAVA, buildScript, additionalRunnerConfig) + static TestProject newJavaProject(String buildScript = GradleScriptTemplates.forJavaProject(), Action additionalRunnerConfig = NONE) { + return new TestProject(ProjectType.JAVA, buildScript, additionalRunnerConfig) } - static TestProjectRule newAndroidProject(String buildScript = GradleScriptTemplates.forAndroidProject(), Action additionalConfig = NONE) { - return new TestProjectRule(ProjectType.ANDROID, buildScript, additionalConfig) + static TestProject newAndroidProject(String buildScript = GradleScriptTemplates.forAndroidProject(), Action additionalConfig = NONE) { + return new TestProject(ProjectType.ANDROID, buildScript, additionalConfig) } - private TestProjectRule(ProjectType project, String buildScript, Action additionalRunnerConfig) { + private TestProject(ProjectType project, String buildScript, Action additionalRunnerConfig) { this.project = project this.buildScript = buildScript this.additionalRunnerConfig = additionalRunnerConfig @@ -42,26 +44,36 @@ class TestProjectRule implements TestRule { @Override Statement apply(Statement base, Description description) { - def methodName = (description.methodName ? "/$description.methodName" : '') - tempFolder = new BuildFolder("test-projects/${description.testClass.canonicalName}${methodName}/test") return new Statement() { @Override void evaluate() throws Throwable { - createSourceCode() - createAndroidManifest() - createBuildScript() - createSettingsScript() + def methodName = (description.methodName ? "/$description.methodName" : '') + def projectPath = "${description.testClass.canonicalName}${methodName}/test" + init(projectPath) base.evaluate() } + + } + } + + void init(String projectPath) { + if (tempFolder != null) { + throw new IllegalStateException("The test project has already been initialised: $tempFolder.root.path.") } + tempFolder = new BuildFolder("test-projects/$projectPath") + createSourceCode() + createAndroidManifest() + createBuildScript() + createSettingsScript() } File getProjectDir() { + checkNotNull(tempFolder, 'The test project has not been initialised yet. Call init() or use it as a test rule.') tempFolder.root } private String createSourceCode() { - new File(tempFolder.root, "src/main/java/HelloWorld.java").with { + new File(projectDir, "src/main/java/HelloWorld.java").with { getParentFile().mkdirs() text = "public class HelloWorld {}" } @@ -69,7 +81,7 @@ class TestProjectRule implements TestRule { private void createAndroidManifest() { if (project == ProjectType.ANDROID) { - new File(tempFolder.root, "/src/main/AndroidManifest.xml").with { + new File(projectDir, "/src/main/AndroidManifest.xml").with { getParentFile().mkdirs() text = "" } @@ -77,13 +89,13 @@ class TestProjectRule implements TestRule { } private void createBuildScript() { - new File(tempFolder.root, "build.gradle").with { + new File(projectDir, 'build.gradle').with { text = buildScript } } private void createSettingsScript() { - new File(tempFolder.root, 'settings.gradle').with { + new File(projectDir, 'settings.gradle').with { text = "rootProject.name = 'test'" } } From 925ce90db4dbdc7cbe29e44b42d26a7e36c1f5bd Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 22:47:11 +0200 Subject: [PATCH 40/73] Add checks from upload task test class into compatibility test class --- .../GradleVersionsCompatibilityTest.groovy | 153 +++++++++++++++++- .../gradle/truth/BuildTaskSubject.groovy | 7 +- .../truth/GradleBuildResultSubject.groovy | 15 +- .../novoda/gradle/truth/GradleTruth.groovy | 6 + 4 files changed, 170 insertions(+), 11 deletions(-) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy index 864d2d5..f3b96ec 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy @@ -1,9 +1,13 @@ package com.novoda.gradle.release +import com.novoda.gradle.test.GradleBuildResult import com.novoda.gradle.test.GradleScriptTemplates import com.novoda.gradle.test.TestProject +import com.novoda.gradle.truth.GradleTruth +import org.gradle.api.file.ConfigurableFileTree +import org.gradle.api.file.FileTree import org.gradle.testkit.runner.GradleRunner -import org.junit.Rule +import org.gradle.testkit.runner.TaskOutcome import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -13,6 +17,15 @@ import static com.google.common.truth.Truth.assertThat @RunWith(Parameterized.class) class GradleVersionsCompatibilityTest { + private static final String BASE_UPLOAD_PATH = 'https://api.bintray.com/content/novoda/maven/test/1.0/com/novoda/test/1.0/test-1.0' + private static final String SOURCES_UPLOAD_PATH = "$BASE_UPLOAD_PATH-sources.jar" + private static final String JAVADOC_UPLOAD_PATH = "$BASE_UPLOAD_PATH-javadoc.jar" + private static final String POM_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.pom" + private static final String JAR_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.jar" + private static final String AAR_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.aar" + + private static final Map RESULTS = [:] + @Parameterized.Parameters(name = "{0}") static Collection configurations() { return [ @@ -31,8 +44,7 @@ class GradleVersionsCompatibilityTest { ] } - @Rule - public final TestProject testProject + private final TestProject testProject private final BuildConfiguration configuration GradleVersionsCompatibilityTest(BuildConfiguration configuration) { @@ -40,10 +52,103 @@ class GradleVersionsCompatibilityTest { this.testProject = configuration.testProject } + private GradleBuildResult getResult() { + if (RESULTS[configuration.key] == null) { + testProject.init("${this.class.canonicalName}/${configuration}/test") + RESULTS[configuration.key] = testProject.execute('clean', 'build', 'bintrayUpload', '-PbintrayKey=key', '-PbintrayUser=user', '--stacktrace') + } + return RESULTS[configuration.key] + } + + @Test + void shouldBuildLibrary() { + GradleTruth.assumeThat(result).isSuccess() + + GradleTruth.assertThat(result.task(':build')).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldGeneratePomFile() { + GradleTruth.assumeThat(result).isSuccess() + + GradleTruth.assertThat(result.task(configuration.generatePomTaskName)).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldGenerateJavadocs() { + GradleTruth.assumeThat(result).isSuccess() + + GradleTruth.assertThat(result.task(configuration.generateJavadocsTaskName)).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldPackageAllGeneratedJavadocs() { + GradleTruth.assumeThat(result).isSuccess() + + GradleTruth.assertThat(result.task(configuration.packageJavadocsTaskName)).hasOutcome(TaskOutcome.SUCCESS) + + ConfigurableFileTree generatedFiles = testProject.fileTree('build/docs/javadoc') + List includePatterns = generatedFiles.collect { '**' + it.path - generatedFiles.dir.path } + FileTree jarfile = testProject.zipTree('build/libs/test-javadoc.jar') + assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) + } + + @Test + void shouldPackageAllSources() { + GradleTruth.assumeThat(result).isSuccess() + + GradleTruth.assertThat(result.task(configuration.packageSourcesTaskName)).hasOutcome(TaskOutcome.SUCCESS) + + ConfigurableFileTree sourceFiles = testProject.fileTree('src/main/java') + List includePatterns = sourceFiles.collect { '**' + it.path - sourceFiles.dir.path } + FileTree jarfile = testProject.zipTree('build/libs/test-sources.jar') + assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) + } + + @Test + void shouldPublishToMavenLocal() { + GradleTruth.assumeThat(result).isSuccess() + + GradleTruth.assertThat(result.task(configuration.publishToMavenLocalTaskName)).hasOutcome(TaskOutcome.SUCCESS) + } + + @Test + void shouldRunUploadTask() { + GradleTruth.assumeThat(result).isSuccess() + + GradleTruth.assertThat(result.task(":bintrayUpload")).hasOutcome(TaskOutcome.SUCCESS) + } + @Test - void shouldMatchExpectedOutcome() { - def result = testProject.execute("build", "bintrayUpload", "-PbintrayKey=key", "-PbintrayUser=user") + void shouldUploadSourcesJar() { + GradleTruth.assumeThat(result).isSuccess() + assertThat(result.output).contains(SOURCES_UPLOAD_PATH) + } + + @Test + void shouldUploadJavadocJar() { + GradleTruth.assumeThat(result).isSuccess() + + assertThat(result.output).contains(JAVADOC_UPLOAD_PATH) + } + + @Test + void shouldUploadPomFile() { + GradleTruth.assumeThat(result).isSuccess() + + assertThat(result.output).contains(POM_UPLOAD_PATH) + } + + @Test + void shouldUploadLibraryArtifact() { + GradleTruth.assumeThat(result).isSuccess() + + assertThat(result.output).contains(configuration.libraryUploadPath) + } + + @Test + void shouldMatchBuildOutcome() { assertThat(result.success).isEqualTo(configuration.expectedBuildSuccess) } @@ -54,13 +159,15 @@ class GradleVersionsCompatibilityTest { static BuildConfiguration forAndroid(String gradleVersion, boolean expectedBuildSuccess) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } - def testProject = TestProject.newAndroidProject(GradleScriptTemplates.forAndroidProject(), additionalRunnerConfig) + def buildScript = GradleScriptTemplates.forAndroidProject() + def testProject = TestProject.newAndroidProject(buildScript, additionalRunnerConfig) return new BuildConfiguration(gradleVersion, testProject, expectedBuildSuccess) } static BuildConfiguration forJava(String gradleVersion, boolean expectedBuildSuccess) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } - def testProject = TestProject.newJavaProject(GradleScriptTemplates.forJavaProject(), additionalRunnerConfig) + def buildScript = GradleScriptTemplates.forJavaProject() + def testProject = TestProject.newJavaProject(buildScript, additionalRunnerConfig) return new BuildConfiguration(gradleVersion, testProject, expectedBuildSuccess) } @@ -74,5 +181,37 @@ class GradleVersionsCompatibilityTest { String toString() { return "${testProject.projectType.capitalize()} project with Gradle $gradleVersion" } + + String getKey() { + return toString() + } + + private boolean isAndroid() { + return testProject.projectType == 'android' + } + + String getGeneratePomTaskName() { + return isAndroid() ? ':generatePomFileForReleasePublication' : ':generatePomFileForMavenPublication' + } + + String getGenerateJavadocsTaskName() { + return isAndroid() ? ':releaseAndroidJavadocs' : ':javadoc' + } + + String getPackageJavadocsTaskName() { + return isAndroid() ? ':releaseAndroidJavadocsJar' : ':mavenJavadocJar' + } + + String getPackageSourcesTaskName() { + return isAndroid() ? ':releaseAndroidSourcesJar' : ':mavenSourcesJar' + } + + String getPublishToMavenLocalTaskName() { + return isAndroid() ? ':publishReleasePublicationToMavenLocal' : ':publishMavenPublicationToMavenLocal' + } + + String getLibraryUploadPath() { + return isAndroid() ? AAR_UPLOAD_PATH : JAR_UPLOAD_PATH + } } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy index 38f846c..7307863 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/truth/BuildTaskSubject.groovy @@ -1,11 +1,14 @@ package com.novoda.gradle.truth + import com.google.common.truth.FailureMetadata import com.google.common.truth.Subject import org.checkerframework.checker.nullness.compatqual.NullableDecl import org.gradle.testkit.runner.BuildTask import org.gradle.testkit.runner.TaskOutcome +import static com.google.common.truth.Fact.fact + class BuildTaskSubject extends Subject { public static final Subject.Factory FACTORY = { metadata, actual -> @@ -17,6 +20,8 @@ class BuildTaskSubject extends Subject { } void hasOutcome(TaskOutcome outcome) { - check().that(actual().outcome).isEqualTo(outcome) + if (actual().outcome != outcome) { + failWithoutActual(fact("expected outcome of task '${actual().path}' to be", outcome), fact('but was', actual().outcome)) + } } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy index 7f36e09..45747e9 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleBuildResultSubject.groovy @@ -1,10 +1,13 @@ package com.novoda.gradle.truth + import com.google.common.truth.FailureMetadata import com.google.common.truth.Subject import com.novoda.gradle.test.GradleBuildResult import org.checkerframework.checker.nullness.compatqual.NullableDecl +import static com.google.common.truth.Fact.fact + class GradleBuildResultSubject extends Subject { public static final Subject.Factory FACTORY = { metadata, actual -> @@ -16,14 +19,20 @@ class GradleBuildResultSubject extends Subject Date: Sat, 27 Oct 2018 22:50:54 +0200 Subject: [PATCH 41/73] Rename compatibility test class --- .../release/BintrayUploadAndroidTest.groovy | 104 ------------------ .../release/BintrayUploadJavaTest.groovy | 103 ----------------- ...tyTest.groovy => ReleasePluginTest.groovy} | 4 +- 3 files changed, 2 insertions(+), 209 deletions(-) delete mode 100644 plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy delete mode 100644 plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy rename plugin/core/src/test/groovy/com/novoda/gradle/release/{GradleVersionsCompatibilityTest.groovy => ReleasePluginTest.groovy} (98%) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy deleted file mode 100644 index ec31470..0000000 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadAndroidTest.groovy +++ /dev/null @@ -1,104 +0,0 @@ -package com.novoda.gradle.release - - -import com.novoda.gradle.test.GradleBuildResult -import com.novoda.gradle.test.TestProject -import com.novoda.gradle.truth.GradleTruth -import org.gradle.api.file.ConfigurableFileTree -import org.gradle.api.file.FileTree -import org.gradle.testkit.runner.TaskOutcome -import org.junit.Before -import org.junit.ClassRule -import org.junit.Test - -import static com.google.common.truth.Truth.assertThat - -class BintrayUploadAndroidTest { - - private static final String BASE_UPLOAD_PATH = 'https://api.bintray.com/content/novoda/maven/test/1.0/com/novoda/test/1.0/test-1.0' - private static final String SOURCES_UPLOAD_PATH = "$BASE_UPLOAD_PATH-sources.jar" - private static final String JAVADOC_UPLOAD_PATH = "$BASE_UPLOAD_PATH-javadoc.jar" - private static final String POM_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.pom" - private static final String AAR_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.aar" - - @ClassRule - public static TestProject testProject = TestProject.newAndroidProject() - private static GradleBuildResult result - - @Before - void setUp() { - if (result == null) { - result = testProject.execute('clean', 'build', ":bintrayUpload", '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') - } - } - - @Test - void shouldBuildLibrary() { - GradleTruth.assertThat(result.task(':build')).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldGeneratePomFile() { - GradleTruth.assertThat(result.task(':generatePomFileForReleasePublication')).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldGenerateJavadocs() { - GradleTruth.assertThat(result.task(':releaseAndroidJavadocs')).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldPackageAllGeneratedJavadocs() { - GradleTruth.assertThat(result.task(':releaseAndroidJavadocsJar')).hasOutcome(TaskOutcome.SUCCESS) - - ConfigurableFileTree generatedFiles = testProject.fileTree('build/docs/javadoc') - List includePatterns = generatedFiles.collect { '**' + it.path - generatedFiles.dir.path } - FileTree jarfile = testProject.zipTree('build/libs/test-javadoc.jar') - assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) - } - - @Test - void shouldPackageAllSources() { - GradleTruth.assertThat(result.task(':releaseAndroidSourcesJar')).hasOutcome(TaskOutcome.SUCCESS) - - ConfigurableFileTree sourceFiles = testProject.fileTree('src/main/java') - List includePatterns = sourceFiles.collect { '**' + it.path - sourceFiles.dir.path } - FileTree jarfile = testProject.zipTree('build/libs/test-sources.jar') - assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) - } - - @Test - void shouldPublishToMavenLocal() { - GradleTruth.assertThat(result.task(':publishReleasePublicationToMavenLocal')).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldRunUploadTask() { - GradleTruth.assertThat(result.task(":bintrayUpload")).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldUploadSourcesJar() { - assertThat(result.output).contains(SOURCES_UPLOAD_PATH) - } - - @Test - void shouldUploadJavadocJar() { - assertThat(result.output).contains(JAVADOC_UPLOAD_PATH) - } - - @Test - void shouldUploadPomFile() { - assertThat(result.output).contains(POM_UPLOAD_PATH) - } - - @Test - void shouldUploadLibraryAar() { - assertThat(result.output).contains(AAR_UPLOAD_PATH) - } - - @Test - void shouldBuildSuccessfully() { - GradleTruth.assertThat(result).isSuccess() - } -} diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy deleted file mode 100644 index 9336366..0000000 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/BintrayUploadJavaTest.groovy +++ /dev/null @@ -1,103 +0,0 @@ -package com.novoda.gradle.release - -import com.novoda.gradle.test.GradleBuildResult -import com.novoda.gradle.test.TestProject -import com.novoda.gradle.truth.GradleTruth -import org.gradle.api.file.ConfigurableFileTree -import org.gradle.api.file.FileTree -import org.gradle.testkit.runner.TaskOutcome -import org.junit.Before -import org.junit.Test - -import static com.google.common.truth.Truth.assertThat - -class BintrayUploadJavaTest { - - private static final String BASE_UPLOAD_PATH = 'https://api.bintray.com/content/novoda/maven/test/1.0/com/novoda/test/1.0/test-1.0' - private static final String SOURCES_UPLOAD_PATH = "$BASE_UPLOAD_PATH-sources.jar" - private static final String JAVADOC_UPLOAD_PATH = "$BASE_UPLOAD_PATH-javadoc.jar" - private static final String POM_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.pom" - private static final String JAR_UPLOAD_PATH = "${BASE_UPLOAD_PATH}.jar" - - private static TestProject testProject - private static GradleBuildResult result - - @Before - void setUp() { - if (testProject == null) { - testProject = TestProject.newJavaProject() - testProject.init("${this.class.canonicalName}/test") - result = testProject.execute('clean', 'build', ":bintrayUpload", '-PbintrayUser=U', '-PbintrayKey=K', '-PdryRun=true', '--stacktrace') - } - } - - @Test - void shouldBuildLibrary() { - GradleTruth.assertThat(result.task(':build')).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldGeneratePomFile() { - GradleTruth.assertThat(result.task(':generatePomFileForMavenPublication')).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldGenerateJavadocs() { - GradleTruth.assertThat(result.task(':javadoc')).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldPackageAllGeneratedJavadocs() { - GradleTruth.assertThat(result.task(':mavenJavadocJar')).hasOutcome(TaskOutcome.SUCCESS) - - ConfigurableFileTree generatedFiles = testProject.fileTree('build/docs/javadoc') - List includePatterns = generatedFiles.collect { '**' + it.path - generatedFiles.dir.path } - FileTree jarfile = testProject.zipTree('build/libs/test-javadoc.jar') - assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) - } - - @Test - void shouldPackageAllSources() { - GradleTruth.assertThat(result.task(':mavenSourcesJar')).hasOutcome(TaskOutcome.SUCCESS) - - ConfigurableFileTree sourceFiles = testProject.fileTree('src/main/java') - List includePatterns = sourceFiles.collect { '**' + it.path - sourceFiles.dir.path } - FileTree jarfile = testProject.zipTree('build/libs/test-sources.jar') - assertThat(jarfile.matching { include includePatterns }).hasSize(includePatterns.size()) - } - - @Test - void shouldPublishToMavenLocal() { - GradleTruth.assertThat(result.task(':publishMavenPublicationToMavenLocal')).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldRunUploadTask() { - GradleTruth.assertThat(result.task(":bintrayUpload")).hasOutcome(TaskOutcome.SUCCESS) - } - - @Test - void shouldUploadSourcesJar() { - assertThat(result.output).contains(SOURCES_UPLOAD_PATH) - } - - @Test - void shouldUploadJavadocJar() { - assertThat(result.output).contains(JAVADOC_UPLOAD_PATH) - } - - @Test - void shouldUploadPomFile() { - assertThat(result.output).contains(POM_UPLOAD_PATH) - } - - @Test - void shouldUploadLibraryJar() { - assertThat(result.output).contains(JAR_UPLOAD_PATH) - } - - @Test - void shouldBuildSuccessfully() { - GradleTruth.assertThat(result).isSuccess() - } -} diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy similarity index 98% rename from plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy rename to plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy index f3b96ec..058748e 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/GradleVersionsCompatibilityTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy @@ -15,7 +15,7 @@ import org.junit.runners.Parameterized import static com.google.common.truth.Truth.assertThat @RunWith(Parameterized.class) -class GradleVersionsCompatibilityTest { +class ReleasePluginTest { private static final String BASE_UPLOAD_PATH = 'https://api.bintray.com/content/novoda/maven/test/1.0/com/novoda/test/1.0/test-1.0' private static final String SOURCES_UPLOAD_PATH = "$BASE_UPLOAD_PATH-sources.jar" @@ -47,7 +47,7 @@ class GradleVersionsCompatibilityTest { private final TestProject testProject private final BuildConfiguration configuration - GradleVersionsCompatibilityTest(BuildConfiguration configuration) { + ReleasePluginTest(BuildConfiguration configuration) { this.configuration = configuration this.testProject = configuration.testProject } From 5adaa7f98a0586fc4665fb94f61e6c4dfbfbb13f Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 11:37:51 +0200 Subject: [PATCH 42/73] Improve API of Artifacts interface --- .../novoda/gradle/release/AndroidArtifacts.groovy | 13 ++++++++----- .../com/novoda/gradle/release/Artifacts.groovy | 2 ++ .../com/novoda/gradle/release/JavaArtifacts.groovy | 10 ++++++---- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy index b665fa3..a229583 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy @@ -1,22 +1,24 @@ package com.novoda.gradle.release import org.gradle.api.Project +import org.gradle.api.component.SoftwareComponent import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.javadoc.Javadoc class AndroidArtifacts implements Artifacts { - def variant + private def variant AndroidArtifacts(variant) { this.variant = variant } + @Override def all(String publicationName, Project project) { [sourcesJar(project), javadocJar(project), mainJar(project)] } - def sourcesJar(Project project) { + private def sourcesJar(Project project) { project.task(variant.name + 'AndroidSourcesJar', type: Jar) { classifier = 'sources' variant.sourceSets.each { @@ -25,7 +27,7 @@ class AndroidArtifacts implements Artifacts { } } - def javadocJar(Project project) { + private def javadocJar(Project project) { def androidJavadocs = project.task(variant.name + 'AndroidJavadocs', type: Javadoc) { variant.sourceSets.each { delegate.source it.java.srcDirs @@ -41,12 +43,13 @@ class AndroidArtifacts implements Artifacts { } } - def mainJar(Project project) { + private def mainJar(Project project) { def archiveBaseName = project.hasProperty("archivesBaseName") ? project.getProperty("archivesBaseName") : project.name "$project.buildDir/outputs/aar/$archiveBaseName-${variant.baseName}.aar" } - def from(Project project) { + @Override + SoftwareComponent from(Project project) { project.components.add(new AndroidLibrary(project)) project.components.android } diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy index b28de39..b3913fd 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy @@ -1,9 +1,11 @@ package com.novoda.gradle.release; import org.gradle.api.Project +import org.gradle.api.component.SoftwareComponent interface Artifacts { def all(String publicationName, Project project) + SoftwareComponent from(Project project) } diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy index d358544..9cc6f2d 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy @@ -1,30 +1,32 @@ package com.novoda.gradle.release import org.gradle.api.Project +import org.gradle.api.component.SoftwareComponent import org.gradle.api.tasks.bundling.Jar class JavaArtifacts implements Artifacts { + @Override def all(String publicationName, Project project) { [sourcesJar(publicationName, project), javadocJar(publicationName, project)] } - def sourcesJar(String publicationName, Project project) { + private def sourcesJar(String publicationName, Project project) { project.task(publicationName + 'SourcesJar', type: Jar) { classifier = 'sources' from project.sourceSets.main.allSource } } - def javadocJar(String publicationName, Project project) { + private def javadocJar(String publicationName, Project project) { project.task(publicationName + 'JavadocJar', type: Jar) { classifier = 'javadoc' from project.files(project.javadoc) } } - def from(Project project) { + @Override + SoftwareComponent from(Project project) { project.components.java } - } From f7f29846393466c8b9c240d68e7b74cda7c70e2f Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 11:49:48 +0200 Subject: [PATCH 43/73] Add explicit types in plugin implementation --- .../gradle/release/ReleasePlugin.groovy | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy index c75393b..95ea37b 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy @@ -3,6 +3,9 @@ package com.novoda.gradle.release import com.jfrog.bintray.gradle.BintrayPlugin import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.component.SoftwareComponent +import org.gradle.api.publish.PublicationContainer +import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication class ReleasePlugin implements Plugin { @@ -19,29 +22,34 @@ class ReleasePlugin implements Plugin { new BintrayPlugin().apply(project) } - void attachArtifacts(PublishExtension extension, Project project) { + private static void attachArtifacts(PublishExtension extension, Project project) { if (project.plugins.hasPlugin('com.android.library')) { project.android.libraryVariants.all { variant -> - def artifactId = extension.artifactId; - addArtifact(project, variant.name, artifactId, new AndroidArtifacts(variant)) + String publicationName = variant.name + attachArtifacts(project, extension, publicationName, new AndroidArtifacts(variant)) } } else { - addArtifact(project, 'maven', project.publish.artifactId, new JavaArtifacts()) + attachArtifacts(project, extension, 'maven', new JavaArtifacts()) } } - void addArtifact(Project project, String name, String artifact, Artifacts artifacts) { - PropertyFinder propertyFinder = new PropertyFinder(project, project.publish) - project.publishing.publications.create(name, MavenPublication) { - groupId project.publish.groupId - artifactId artifact - version = propertyFinder.publishVersion + private static void attachArtifacts(Project project, PublishExtension extension, String publicationName, Artifacts artifacts) { + PropertyFinder propertyFinder = new PropertyFinder(project, extension) + String groupId = extension.groupId + String artifactId = extension.artifactId + String version = propertyFinder.publishVersion - artifacts.all(it.name, project).each { - delegate.artifact it - } - from artifacts.from(project) + def artifactSources = artifacts.all(publicationName, project) + SoftwareComponent softwareComponent = artifacts.from(project) + + PublicationContainer publicationContainer = project.extensions.getByType(PublishingExtension).publications + publicationContainer.create(publicationName, MavenPublication) { MavenPublication publication -> + publication.groupId = groupId + publication.artifactId = artifactId + publication.version = version + artifactSources.each { publication.artifact it } + publication.from softwareComponent } } } From 516de80938bb08c2c1096ded6f052e75c1070eca Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 13:11:57 +0200 Subject: [PATCH 44/73] Introduce new interface to replace Artifacts --- plugin/core/build.gradle | 1 - .../gradle/release/JavaAttachments.groovy | 47 +++++++++++++++++++ .../MavenPublicationAttachments.groovy | 17 +++++++ .../gradle/release/ReleasePlugin.groovy | 22 +++++---- 4 files changed, 77 insertions(+), 10 deletions(-) create mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/JavaAttachments.groovy create mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/MavenPublicationAttachments.groovy diff --git a/plugin/core/build.gradle b/plugin/core/build.gradle index ee51161..015878d 100644 --- a/plugin/core/build.gradle +++ b/plugin/core/build.gradle @@ -1,7 +1,6 @@ plugins { id 'groovy' id 'java-gradle-plugin' - id 'maven' id 'com.novoda.build-properties' version '0.4.1' } diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaAttachments.groovy new file mode 100644 index 0000000..0d0adaf --- /dev/null +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaAttachments.groovy @@ -0,0 +1,47 @@ +package com.novoda.gradle.release + +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.component.SoftwareComponent +import org.gradle.api.tasks.SourceSetContainer +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.javadoc.Javadoc + +class JavaAttachments extends MavenPublicationAttachments { + + private final String publicationName + private final Project project + private final List allArtifactSources + + JavaAttachments(String publicationName, Project project) { + this.publicationName = publicationName + this.project = project + this.allArtifactSources = Arrays.asList(publicationSourcesJar(), publicationJavadocJar()).asImmutable() + } + + private Task publicationSourcesJar() { + SourceSetContainer sourceSets = project.sourceSets + return project.task("${publicationName}SourcesJar", type: Jar) { Jar jar -> + jar.classifier = 'sources' + jar.from sourceSets.getByName('main').allJava.flatten() + } + } + + private Task publicationJavadocJar() { + Javadoc javadoc = project.javadoc + return project.task("${publicationName}JavadocJar", type: Jar) { Jar jar -> + jar.classifier = 'javadoc' + jar.from project.files(javadoc) + } + } + + @Override + List getAllArtifactSources() { + return allArtifactSources + } + + @Override + SoftwareComponent getSoftwareComponent() { + return project.components.getByName('java') + } +} diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/MavenPublicationAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/MavenPublicationAttachments.groovy new file mode 100644 index 0000000..5c9ec5f --- /dev/null +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/MavenPublicationAttachments.groovy @@ -0,0 +1,17 @@ +package com.novoda.gradle.release + + +import org.gradle.api.component.SoftwareComponent +import org.gradle.api.publish.maven.MavenPublication + +abstract class MavenPublicationAttachments { + + abstract List getAllArtifactSources() + + abstract SoftwareComponent getSoftwareComponent() + + void attachTo(MavenPublication publication) { + allArtifactSources.each { publication.artifact it } + publication.from softwareComponent + } +} diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy index 95ea37b..6a60eda 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy @@ -29,27 +29,31 @@ class ReleasePlugin implements Plugin { attachArtifacts(project, extension, publicationName, new AndroidArtifacts(variant)) } } else { - attachArtifacts(project, extension, 'maven', new JavaArtifacts()) + String publicationName = 'maven' + MavenPublication publication = createPublication(publicationName, project, extension) + new JavaAttachments(publicationName, project).attachTo(publication) } } - private static void attachArtifacts(Project project, PublishExtension extension, String publicationName, Artifacts artifacts) { + MavenPublication publication = createPublication(publicationName, project, extension) + def artifactSources = artifacts.all(publicationName, project) + SoftwareComponent softwareComponent = artifacts.from(project) + artifactSources.each { publication.artifact it } + publication.from softwareComponent + } + + private static MavenPublication createPublication(String publicationName, Project project, PublishExtension extension) { PropertyFinder propertyFinder = new PropertyFinder(project, extension) String groupId = extension.groupId String artifactId = extension.artifactId String version = propertyFinder.publishVersion - def artifactSources = artifacts.all(publicationName, project) - SoftwareComponent softwareComponent = artifacts.from(project) - PublicationContainer publicationContainer = project.extensions.getByType(PublishingExtension).publications - publicationContainer.create(publicationName, MavenPublication) { MavenPublication publication -> + return publicationContainer.create(publicationName, MavenPublication) { MavenPublication publication -> publication.groupId = groupId publication.artifactId = artifactId publication.version = version - artifactSources.each { publication.artifact it } - publication.from softwareComponent - } + } as MavenPublication } } From aaabf58d691eac41a9b20327e8e5e0103a58d7c0 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 23:48:47 +0200 Subject: [PATCH 45/73] Change task names to be more in line with Gradle convetion --- .../release/{ => internal}/JavaAttachments.groovy | 13 +++++++------ .../novoda/gradle/release/ReleasePluginTest.groovy | 14 +++++++++----- 2 files changed, 16 insertions(+), 11 deletions(-) rename plugin/core/src/main/groovy/com/novoda/gradle/release/{ => internal}/JavaAttachments.groovy (70%) diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/JavaAttachments.groovy similarity index 70% rename from plugin/core/src/main/groovy/com/novoda/gradle/release/JavaAttachments.groovy rename to plugin/core/src/main/groovy/com/novoda/gradle/release/internal/JavaAttachments.groovy index 0d0adaf..e43e013 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/JavaAttachments.groovy @@ -1,10 +1,11 @@ -package com.novoda.gradle.release +package com.novoda.gradle.release.internal +import com.novoda.gradle.release.MavenPublicationAttachments import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.component.SoftwareComponent -import org.gradle.api.tasks.SourceSetContainer import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.javadoc.Javadoc class JavaAttachments extends MavenPublicationAttachments { @@ -20,16 +21,16 @@ class JavaAttachments extends MavenPublicationAttachments { } private Task publicationSourcesJar() { - SourceSetContainer sourceSets = project.sourceSets - return project.task("${publicationName}SourcesJar", type: Jar) { Jar jar -> + JavaCompile javaCompile = project.compileJava + return project.task("genereateSourcesJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> jar.classifier = 'sources' - jar.from sourceSets.getByName('main').allJava.flatten() + jar.from javaCompile.source } } private Task publicationJavadocJar() { Javadoc javadoc = project.javadoc - return project.task("${publicationName}JavadocJar", type: Jar) { Jar jar -> + return project.task("genereateJavadocsJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> jar.classifier = 'javadoc' jar.from project.files(javadoc) } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy index 058748e..2a54255 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy @@ -190,24 +190,28 @@ class ReleasePluginTest { return testProject.projectType == 'android' } + private String getPublicationName() { + return isAndroid() ? 'release' : 'maven' + } + String getGeneratePomTaskName() { - return isAndroid() ? ':generatePomFileForReleasePublication' : ':generatePomFileForMavenPublication' + return ":generatePomFileFor${publicationName.capitalize()}Publication" } String getGenerateJavadocsTaskName() { - return isAndroid() ? ':releaseAndroidJavadocs' : ':javadoc' + return isAndroid() ? ":javadoc${publicationName.capitalize()}" : ':javadoc' } String getPackageJavadocsTaskName() { - return isAndroid() ? ':releaseAndroidJavadocsJar' : ':mavenJavadocJar' + return ":genereateJavadocsJarFor${publicationName.capitalize()}Publication" } String getPackageSourcesTaskName() { - return isAndroid() ? ':releaseAndroidSourcesJar' : ':mavenSourcesJar' + return ":genereateSourcesJarFor${publicationName.capitalize()}Publication" } String getPublishToMavenLocalTaskName() { - return isAndroid() ? ':publishReleasePublicationToMavenLocal' : ':publishMavenPublicationToMavenLocal' + return ":publish${publicationName.capitalize()}PublicationToMavenLocal" } String getLibraryUploadPath() { From fc8abf77b79629d5ea2b17b546acbcff0fb73032 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 00:09:25 +0200 Subject: [PATCH 46/73] Introduce placeholder implementation of MavenPublicationAttachements for Androd projects --- .../gradle/release/ReleasePlugin.groovy | 19 +++--- .../internal/AndroidAttachments.groovy | 63 +++++++++++++++++++ 2 files changed, 70 insertions(+), 12 deletions(-) create mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy index 6a60eda..6b7ed1c 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/ReleasePlugin.groovy @@ -1,9 +1,10 @@ package com.novoda.gradle.release import com.jfrog.bintray.gradle.BintrayPlugin +import com.novoda.gradle.release.internal.AndroidAttachments +import com.novoda.gradle.release.internal.JavaAttachments import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.component.SoftwareComponent import org.gradle.api.publish.PublicationContainer import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication @@ -23,26 +24,20 @@ class ReleasePlugin implements Plugin { } private static void attachArtifacts(PublishExtension extension, Project project) { - if (project.plugins.hasPlugin('com.android.library')) { + project.plugins.withId('com.android.library') { project.android.libraryVariants.all { variant -> String publicationName = variant.name - attachArtifacts(project, extension, publicationName, new AndroidArtifacts(variant)) + MavenPublication publication = createPublication(publicationName, project, extension) + new AndroidAttachments(publicationName, project, variant).attachTo(publication) } - } else { + } + project.plugins.withId('java') { String publicationName = 'maven' MavenPublication publication = createPublication(publicationName, project, extension) new JavaAttachments(publicationName, project).attachTo(publication) } } - private static void attachArtifacts(Project project, PublishExtension extension, String publicationName, Artifacts artifacts) { - MavenPublication publication = createPublication(publicationName, project, extension) - def artifactSources = artifacts.all(publicationName, project) - SoftwareComponent softwareComponent = artifacts.from(project) - artifactSources.each { publication.artifact it } - publication.from softwareComponent - } - private static MavenPublication createPublication(String publicationName, Project project, PublishExtension extension) { PropertyFinder propertyFinder = new PropertyFinder(project, extension) String groupId = extension.groupId diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy new file mode 100644 index 0000000..8509721 --- /dev/null +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy @@ -0,0 +1,63 @@ +package com.novoda.gradle.release.internal + +import com.novoda.gradle.release.MavenPublicationAttachments +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.component.SoftwareComponent +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.javadoc.Javadoc + +class AndroidAttachments extends MavenPublicationAttachments { + + private final String publicationName + private final Project project + private final def variant + private final List allArtifactSources + + AndroidAttachments(String publicationName, Project project, def variant) { + this.publicationName = publicationName + this.variant = variant + this.project = project + this.allArtifactSources = Arrays.asList(publicationSourcesJar(), publicationJavadocJar()).asImmutable() + } + + private Task publicationSourcesJar() { + project.task("genereateSourcesJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> + jar.classifier = 'sources' + jar.from variant.sourceSets.collect { it.javaDirectories }.flatten() + } + } + + private Task publicationJavadocJar() { + Javadoc javadoc = project.task("javadoc${variant.name.capitalize()}", type: Javadoc) { Javadoc javadoc -> + javadoc.source = variant.javaCompiler.source + javadoc.classpath = variant.javaCompiler.classpath + } as Javadoc + return project.task("genereateJavadocsJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> + jar.classifier = 'javadoc' + jar.from project.files(javadoc) + } + } + + @Override + List getAllArtifactSources() { + return allArtifactSources + } + + @Override + SoftwareComponent getSoftwareComponent() { + return new SoftwareComponentInternal() { + @Override + Set getUsages() { + return Collections.emptySet() + } + + @Override + String getName() { + return 'android' + } + } + } +} From cc88cf6245c9d470d704edbbe9918355797b6fb1 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 00:32:58 +0200 Subject: [PATCH 47/73] Add aar as artifact for Android library projects --- .../gradle/release/internal/AndroidAttachments.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy index 8509721..aafccc4 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy @@ -20,7 +20,7 @@ class AndroidAttachments extends MavenPublicationAttachments { this.publicationName = publicationName this.variant = variant this.project = project - this.allArtifactSources = Arrays.asList(publicationSourcesJar(), publicationJavadocJar()).asImmutable() + this.allArtifactSources = Arrays.asList(publicationSourcesJar(), publicationJavadocJar(), archivePath()).asImmutable() } private Task publicationSourcesJar() { @@ -41,6 +41,10 @@ class AndroidAttachments extends MavenPublicationAttachments { } } + private void archivePath() { + variant.packageLibrary.archivePath + } + @Override List getAllArtifactSources() { return allArtifactSources From 18f052e945d42850c1e6460ccffdc469db8261cc Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 01:25:42 +0200 Subject: [PATCH 48/73] Add implementation of SoftwareComponentInternal for Android library projects --- .../internal/AndroidAttachments.groovy | 18 +--- .../internal/AndroidSoftwareComponent.groovy | 100 ++++++++++++++++++ 2 files changed, 103 insertions(+), 15 deletions(-) create mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidSoftwareComponent.groovy diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy index aafccc4..4402665 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy @@ -4,8 +4,6 @@ import com.novoda.gradle.release.MavenPublicationAttachments import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.component.SoftwareComponent -import org.gradle.api.internal.component.SoftwareComponentInternal -import org.gradle.api.internal.component.UsageContext import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.javadoc.Javadoc @@ -41,8 +39,8 @@ class AndroidAttachments extends MavenPublicationAttachments { } } - private void archivePath() { - variant.packageLibrary.archivePath + private def archivePath() { + return variant.packageLibrary.archivePath } @Override @@ -52,16 +50,6 @@ class AndroidAttachments extends MavenPublicationAttachments { @Override SoftwareComponent getSoftwareComponent() { - return new SoftwareComponentInternal() { - @Override - Set getUsages() { - return Collections.emptySet() - } - - @Override - String getName() { - return 'android' - } - } + return new AndroidSoftwareComponent(project.objects, project.configurations) } } diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidSoftwareComponent.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidSoftwareComponent.groovy new file mode 100644 index 0000000..a9ab505 --- /dev/null +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidSoftwareComponent.groovy @@ -0,0 +1,100 @@ +package com.novoda.gradle.release.internal + +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.artifacts.DependencySet +import org.gradle.api.artifacts.ModuleDependency +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.attributes.Usage +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext +import org.gradle.api.model.ObjectFactory +import org.gradle.util.GradleVersion + +class AndroidSoftwareComponent implements SoftwareComponentInternal { + + private final Set usageContexts + private final ConfigurationContainer configurations + + AndroidSoftwareComponent(ObjectFactory objectFactory, ConfigurationContainer configurations) { + this.configurations = configurations + this.usageContexts = generateUsageContexts(objectFactory).asImmutable() + } + + // Using the new Usage in 4.1 will make the plugin crash + // as comparing logic is still using the old Usage. + // For more details: https://github.com/novoda/bintray-release/pull/147 + private Set generateUsageContexts(ObjectFactory objectFactory) { + def isNewerThan4_1 = GradleVersion.current() > GradleVersion.version("4.1") + Usage runtime = objectFactory.named(Usage.class, isNewerThan4_1 ? Usage.JAVA_RUNTIME : "for runtime") + Usage compile = objectFactory.named(Usage.class, isNewerThan4_1 ? Usage.JAVA_API : "for compile") + RuntimeUsageContext runtimeUsage = new RuntimeUsageContext(runtime) + CompileUsageContext compileUsage = new CompileUsageContext(compile) + return [runtimeUsage, compileUsage] as Set + } + + @Override + Set getUsages() { + return usageContexts + } + + @Override + String getName() { + return 'android' + } + + private class RuntimeUsageContext implements UsageContext { + + private final Usage usage + private DependencySet dependencies + + RuntimeUsageContext(Usage usage) { + this.usage = usage + } + + @Override + Usage getUsage() { + return usage + } + + @Override + Set getArtifacts() { + return Collections.emptySet() + } + + @Override + Set getDependencies() { + if (dependencies == null) { + dependencies = configurations.getByName('implementation').getAllDependencies() + } + return dependencies.withType(ModuleDependency.class) + } + } + + private class CompileUsageContext implements UsageContext { + + private final Usage usage + private DependencySet dependencies + + CompileUsageContext(Usage usage) { + this.usage = usage + } + + @Override + Usage getUsage() { + return usage + } + + @Override + Set getArtifacts() { + return Collections.emptySet() + } + + @Override + Set getDependencies() { + if (dependencies == null) { + dependencies = (configurations.findByName('api') ?: configurations.getByName('compile')).getAllDependencies() + } + return dependencies.withType(ModuleDependency.class) + } + } +} From c565e6a2740782eec5d9e3e87823faf033bd08e9 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 01:47:51 +0200 Subject: [PATCH 49/73] Use correct precondition in functional tests --- .../gradle/release/ReleasePluginTest.groovy | 22 +++++++++---------- .../gradle/test/GradleScriptTemplates.groovy | 3 ++- .../novoda/gradle/truth/GradleTruth.groovy | 6 ++--- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy index 2a54255..1e9fead 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy @@ -62,28 +62,28 @@ class ReleasePluginTest { @Test void shouldBuildLibrary() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() GradleTruth.assertThat(result.task(':build')).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldGeneratePomFile() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() GradleTruth.assertThat(result.task(configuration.generatePomTaskName)).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldGenerateJavadocs() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() GradleTruth.assertThat(result.task(configuration.generateJavadocsTaskName)).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldPackageAllGeneratedJavadocs() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() GradleTruth.assertThat(result.task(configuration.packageJavadocsTaskName)).hasOutcome(TaskOutcome.SUCCESS) @@ -95,7 +95,7 @@ class ReleasePluginTest { @Test void shouldPackageAllSources() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() GradleTruth.assertThat(result.task(configuration.packageSourcesTaskName)).hasOutcome(TaskOutcome.SUCCESS) @@ -107,42 +107,42 @@ class ReleasePluginTest { @Test void shouldPublishToMavenLocal() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() GradleTruth.assertThat(result.task(configuration.publishToMavenLocalTaskName)).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldRunUploadTask() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() GradleTruth.assertThat(result.task(":bintrayUpload")).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldUploadSourcesJar() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() assertThat(result.output).contains(SOURCES_UPLOAD_PATH) } @Test void shouldUploadJavadocJar() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() assertThat(result.output).contains(JAVADOC_UPLOAD_PATH) } @Test void shouldUploadPomFile() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() assertThat(result.output).contains(POM_UPLOAD_PATH) } @Test void shouldUploadLibraryArtifact() { - GradleTruth.assumeThat(result).isSuccess() + GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() assertThat(result.output).contains(configuration.libraryUploadPath) } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy index a8f27a2..11c3901 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy @@ -31,8 +31,8 @@ class GradleScriptTemplates { return """ buildscript { repositories { - jcenter() google() + jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.0.0' @@ -61,6 +61,7 @@ class GradleScriptTemplates { } repositories { + google() jcenter() } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy index bc251d6..a749bd4 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/truth/GradleTruth.groovy @@ -1,6 +1,6 @@ package com.novoda.gradle.truth - +import com.google.common.truth.BooleanSubject import com.google.common.truth.TruthJUnit import com.novoda.gradle.test.GradleBuildResult import org.gradle.testkit.runner.BuildTask @@ -13,8 +13,8 @@ final class GradleTruth { return assertAbout(GradleBuildResultSubject.FACTORY).that(result) } - static GradleBuildResultSubject assumeThat(GradleBuildResult result) { - return TruthJUnit.assume().about(GradleBuildResultSubject.FACTORY).that(result) + static BooleanSubject assumeThat(boolean flag) { + return TruthJUnit.assume().that(flag) } static BuildTaskSubject assertThat(BuildTask actual) { From 6a3b84504325241d07b875ef726894840b61d580 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 01:49:19 +0200 Subject: [PATCH 50/73] Remove obsolete taxonomy of Artifacts --- .../gradle/release/AndroidArtifacts.groovy | 57 ------------- .../gradle/release/AndroidLibrary.groovy | 82 ------------------- .../novoda/gradle/release/Artifacts.groovy | 11 --- .../gradle/release/JavaArtifacts.groovy | 32 -------- 4 files changed, 182 deletions(-) delete mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy delete mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy delete mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy delete mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy deleted file mode 100644 index a229583..0000000 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidArtifacts.groovy +++ /dev/null @@ -1,57 +0,0 @@ -package com.novoda.gradle.release - -import org.gradle.api.Project -import org.gradle.api.component.SoftwareComponent -import org.gradle.api.tasks.bundling.Jar -import org.gradle.api.tasks.javadoc.Javadoc - -class AndroidArtifacts implements Artifacts { - - private def variant - - AndroidArtifacts(variant) { - this.variant = variant - } - - @Override - def all(String publicationName, Project project) { - [sourcesJar(project), javadocJar(project), mainJar(project)] - } - - private def sourcesJar(Project project) { - project.task(variant.name + 'AndroidSourcesJar', type: Jar) { - classifier = 'sources' - variant.sourceSets.each { - from it.java.srcDirs - } - } - } - - private def javadocJar(Project project) { - def androidJavadocs = project.task(variant.name + 'AndroidJavadocs', type: Javadoc) { - variant.sourceSets.each { - delegate.source it.java.srcDirs - } - classpath += project.files(project.android.getBootClasspath().join(File.pathSeparator)) - classpath += variant.javaCompile.classpath - classpath += variant.javaCompile.outputs.files - } - - project.task(variant.name + 'AndroidJavadocsJar', type: Jar) { - classifier = 'javadoc' - from project.files(androidJavadocs) - } - } - - private def mainJar(Project project) { - def archiveBaseName = project.hasProperty("archivesBaseName") ? project.getProperty("archivesBaseName") : project.name - "$project.buildDir/outputs/aar/$archiveBaseName-${variant.baseName}.aar" - } - - @Override - SoftwareComponent from(Project project) { - project.components.add(new AndroidLibrary(project)) - project.components.android - } - -} diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy deleted file mode 100644 index 6889b8e..0000000 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/AndroidLibrary.groovy +++ /dev/null @@ -1,82 +0,0 @@ -package com.novoda.gradle.release - -import org.gradle.api.DomainObjectSet -import org.gradle.api.Project -import org.gradle.api.UnknownDomainObjectException -import org.gradle.api.artifacts.Dependency -import org.gradle.api.artifacts.ModuleDependency -import org.gradle.api.artifacts.PublishArtifact -import org.gradle.api.attributes.Usage -import org.gradle.api.internal.DefaultDomainObjectSet -import org.gradle.api.internal.component.SoftwareComponentInternal -import org.gradle.api.internal.component.UsageContext -import org.gradle.api.model.ObjectFactory -import org.gradle.util.GradleVersion - -class AndroidLibrary implements SoftwareComponentInternal { - - private final String CONF_COMPILE = "compile" - private final String CONF_API = "api" - private final String CONF_IMPLEMENTATION = "implementation" - - private final Set usages = new DefaultDomainObjectSet(UsageContext) - - AndroidLibrary(Project project) { - ObjectFactory objectFactory = project.getObjects() - - // Using the new Usage in 4.1 will make the plugin crash - // as comparing logic is still using the old Usage. - // For more details: https://github.com/novoda/bintray-release/pull/147 - def isNewerThan4_1 = GradleVersion.current() > GradleVersion.version("4.1") - Usage api = objectFactory.named(Usage.class, isNewerThan4_1 ? Usage.JAVA_API : "for compile") - Usage runtime = objectFactory.named(Usage.class, isNewerThan4_1 ? Usage.JAVA_RUNTIME : "for runtime") - - addUsageContextFromConfiguration(project, CONF_COMPILE, api) - addUsageContextFromConfiguration(project, CONF_API, api) - addUsageContextFromConfiguration(project, CONF_IMPLEMENTATION, runtime) - } - - String getName() { - return "android" - } - - Set getUsages() { - return usages - } - - private addUsageContextFromConfiguration(Project project, String configuration, Usage usage) { - try { - def configurationObj = project.configurations.getByName(configuration) - def dependency = configurationObj.dependencies - if (!dependency.isEmpty()) { - def libraryUsage = new LibraryUsage(dependency, usage) - usages.add(libraryUsage) - } - } catch (UnknownDomainObjectException ignore) { - // cannot find configuration - } - } - - private static class LibraryUsage implements UsageContext { - - private final DomainObjectSet dependencies - private final Usage usage - - LibraryUsage(DomainObjectSet dependencies, Usage usage) { - this.usage = usage - this.dependencies = dependencies - } - - Usage getUsage() { - return usage - } - - Set getArtifacts() { - new LinkedHashSet() - } - - Set getDependencies() { - dependencies.withType(ModuleDependency) - } - } -} diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy deleted file mode 100644 index b3913fd..0000000 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/Artifacts.groovy +++ /dev/null @@ -1,11 +0,0 @@ -package com.novoda.gradle.release; - -import org.gradle.api.Project -import org.gradle.api.component.SoftwareComponent - -interface Artifacts { - - def all(String publicationName, Project project) - - SoftwareComponent from(Project project) -} diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy deleted file mode 100644 index 9cc6f2d..0000000 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/JavaArtifacts.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package com.novoda.gradle.release - -import org.gradle.api.Project -import org.gradle.api.component.SoftwareComponent -import org.gradle.api.tasks.bundling.Jar - -class JavaArtifacts implements Artifacts { - - @Override - def all(String publicationName, Project project) { - [sourcesJar(publicationName, project), javadocJar(publicationName, project)] - } - - private def sourcesJar(String publicationName, Project project) { - project.task(publicationName + 'SourcesJar', type: Jar) { - classifier = 'sources' - from project.sourceSets.main.allSource - } - } - - private def javadocJar(String publicationName, Project project) { - project.task(publicationName + 'JavadocJar', type: Jar) { - classifier = 'javadoc' - from project.files(project.javadoc) - } - } - - @Override - SoftwareComponent from(Project project) { - project.components.java - } -} From fa789885cba26c0cb11cffff878fa517b796649c Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 10:06:22 +0100 Subject: [PATCH 51/73] Tweak MavenPublicationAttachments API to promote constructor injection in implementations --- .../MavenPublicationAttachments.groovy | 35 ++++++++++++-- .../internal/AndroidAttachments.groovy | 48 ++++++------------- .../release/internal/JavaAttachments.groovy | 39 +++++---------- 3 files changed, 56 insertions(+), 66 deletions(-) diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/MavenPublicationAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/MavenPublicationAttachments.groovy index 5c9ec5f..53d94ce 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/MavenPublicationAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/MavenPublicationAttachments.groovy @@ -1,17 +1,42 @@ package com.novoda.gradle.release - +import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.component.SoftwareComponent import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.bundling.Jar +import org.gradle.api.tasks.javadoc.Javadoc + +class MavenPublicationAttachments { -abstract class MavenPublicationAttachments { + private final SoftwareComponent softwareComponent + private final List allArtifactSources - abstract List getAllArtifactSources() + MavenPublicationAttachments(SoftwareComponent softwareComponent, def ... allArtifactSources) { + this(softwareComponent, Arrays.asList(allArtifactSources).asImmutable()) + } - abstract SoftwareComponent getSoftwareComponent() + MavenPublicationAttachments(SoftwareComponent softwareComponent, List allArtifactSources) { + this.softwareComponent = softwareComponent + this.allArtifactSources = allArtifactSources + } - void attachTo(MavenPublication publication) { + final void attachTo(MavenPublication publication) { allArtifactSources.each { publication.artifact it } publication.from softwareComponent } + + protected static Task sourcesJarTask(Project project, String publicationName, def ... sourcePaths) { + return project.task("genereateSourcesJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> + jar.classifier = 'sources' + jar.from sourcePaths + } + } + + protected static Task javadocsJarTask(Project project, String publicationName, Javadoc javadoc) { + return project.task("genereateJavadocsJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> + jar.classifier = 'javadoc' + jar.from project.files(javadoc) + } + } } diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy index 4402665..b14ee3d 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy @@ -3,53 +3,35 @@ package com.novoda.gradle.release.internal import com.novoda.gradle.release.MavenPublicationAttachments import org.gradle.api.Project import org.gradle.api.Task -import org.gradle.api.component.SoftwareComponent -import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.javadoc.Javadoc class AndroidAttachments extends MavenPublicationAttachments { - private final String publicationName - private final Project project - private final def variant - private final List allArtifactSources - AndroidAttachments(String publicationName, Project project, def variant) { - this.publicationName = publicationName - this.variant = variant - this.project = project - this.allArtifactSources = Arrays.asList(publicationSourcesJar(), publicationJavadocJar(), archivePath()).asImmutable() + super(androidComponentFrom(project), + androidSourcesJarTask(project, publicationName, variant), + androidJavadocsJarTask(project, publicationName, variant), + androidArchivePath(variant)) } - private Task publicationSourcesJar() { - project.task("genereateSourcesJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> - jar.classifier = 'sources' - jar.from variant.sourceSets.collect { it.javaDirectories }.flatten() - } + private static AndroidSoftwareComponent androidComponentFrom(Project project) { + return new AndroidSoftwareComponent(project.objects, project.configurations) } - private Task publicationJavadocJar() { - Javadoc javadoc = project.task("javadoc${variant.name.capitalize()}", type: Javadoc) { Javadoc javadoc -> + private static Task androidSourcesJarTask(Project project, String publicationName, def variant) { + def sourcePaths = variant.sourceSets.collect { it.javaDirectories }.flatten() + return sourcesJarTask(project, publicationName, sourcePaths) + } + + private static Task androidJavadocsJarTask(Project project, String publicationName, def variant) { + Javadoc javadoc = project.task("javadoc${publicationName.capitalize()}", type: Javadoc) { Javadoc javadoc -> javadoc.source = variant.javaCompiler.source javadoc.classpath = variant.javaCompiler.classpath } as Javadoc - return project.task("genereateJavadocsJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> - jar.classifier = 'javadoc' - jar.from project.files(javadoc) - } + return javadocsJarTask(project, publicationName, javadoc) } - private def archivePath() { + private static def androidArchivePath(def variant) { return variant.packageLibrary.archivePath } - - @Override - List getAllArtifactSources() { - return allArtifactSources - } - - @Override - SoftwareComponent getSoftwareComponent() { - return new AndroidSoftwareComponent(project.objects, project.configurations) - } } diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/JavaAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/JavaAttachments.groovy index e43e013..6cb0a0e 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/JavaAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/JavaAttachments.groovy @@ -4,45 +4,28 @@ import com.novoda.gradle.release.MavenPublicationAttachments import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.component.SoftwareComponent -import org.gradle.api.tasks.bundling.Jar import org.gradle.api.tasks.compile.JavaCompile import org.gradle.api.tasks.javadoc.Javadoc class JavaAttachments extends MavenPublicationAttachments { - private final String publicationName - private final Project project - private final List allArtifactSources - JavaAttachments(String publicationName, Project project) { - this.publicationName = publicationName - this.project = project - this.allArtifactSources = Arrays.asList(publicationSourcesJar(), publicationJavadocJar()).asImmutable() + super(javaComponentFrom(project), + javaSourcesJarTask(project, publicationName), + javaJavadocsJarTask(project, publicationName)) } - private Task publicationSourcesJar() { - JavaCompile javaCompile = project.compileJava - return project.task("genereateSourcesJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> - jar.classifier = 'sources' - jar.from javaCompile.source - } - } - - private Task publicationJavadocJar() { - Javadoc javadoc = project.javadoc - return project.task("genereateJavadocsJarFor${publicationName.capitalize()}Publication", type: Jar) { Jar jar -> - jar.classifier = 'javadoc' - jar.from project.files(javadoc) - } + private static SoftwareComponent javaComponentFrom(Project project) { + return project.components.getByName('java') } - @Override - List getAllArtifactSources() { - return allArtifactSources + private static Task javaSourcesJarTask(Project project, String publicationName) { + JavaCompile javaCompile = project.compileJava + return sourcesJarTask(project, publicationName, javaCompile.source) } - @Override - SoftwareComponent getSoftwareComponent() { - return project.components.getByName('java') + private static Task javaJavadocsJarTask(Project project, String publicationName) { + Javadoc javadoc = project.javadoc + return javadocsJarTask(project, publicationName, javadoc) } } From afc979fb23f96206a754a9b3b09e285a0dc3eade Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 10:40:45 +0200 Subject: [PATCH 52/73] Add module using Gradle 4.1 API --- plugin/compat/common.gradle | 11 +++++++++++ plugin/compat/gradle-4_1/build.gradle | 6 ++++++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54727 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 +++++ plugin/compat/gradle-4_1/setting.gradle | 1 + 5 files changed, 23 insertions(+) create mode 100644 plugin/compat/common.gradle create mode 100644 plugin/compat/gradle-4_1/build.gradle create mode 100644 plugin/compat/gradle-4_1/gradle/wrapper/gradle-wrapper.jar create mode 100644 plugin/compat/gradle-4_1/gradle/wrapper/gradle-wrapper.properties create mode 100644 plugin/compat/gradle-4_1/setting.gradle diff --git a/plugin/compat/common.gradle b/plugin/compat/common.gradle new file mode 100644 index 0000000..39a68b1 --- /dev/null +++ b/plugin/compat/common.gradle @@ -0,0 +1,11 @@ +buildscript { + repositories { + mavenCentral() + } +} + +apply plugin: 'groovy' + +dependencies { + compile gradleApi() +} diff --git a/plugin/compat/gradle-4_1/build.gradle b/plugin/compat/gradle-4_1/build.gradle new file mode 100644 index 0000000..cd0695a --- /dev/null +++ b/plugin/compat/gradle-4_1/build.gradle @@ -0,0 +1,6 @@ +apply from: '../common.gradle' + +task wrapper(type: Wrapper) { + distributionType = Wrapper.DistributionType.ALL + gradleVersion = '4.1' +} diff --git a/plugin/compat/gradle-4_1/gradle/wrapper/gradle-wrapper.jar b/plugin/compat/gradle-4_1/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..27768f1bbac3ce2d055b20d521f12da78d331e8e GIT binary patch literal 54727 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

i| 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 literal 0 HcmV?d00001 diff --git a/plugin/compat/gradle-4_1/gradle/wrapper/gradle-wrapper.properties b/plugin/compat/gradle-4_1/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..bf1b63c --- /dev/null +++ b/plugin/compat/gradle-4_1/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip diff --git a/plugin/compat/gradle-4_1/setting.gradle b/plugin/compat/gradle-4_1/setting.gradle new file mode 100644 index 0000000..0eb854a --- /dev/null +++ b/plugin/compat/gradle-4_1/setting.gradle @@ -0,0 +1 @@ +rootProject.name = 'compat-gradle-4_1' From 0c2e88526fdd856a90dba169982c2d80571457c3 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 27 Oct 2018 11:31:15 +0200 Subject: [PATCH 53/73] Add placeholder implementation of SoftwareComponentInternal compatible with Gradle 4.1 --- .../AndroidLibraryCompatGradle_4_1.groovy | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/android/compat/gradle4_1/AndroidLibraryCompatGradle_4_1.groovy diff --git a/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/android/compat/gradle4_1/AndroidLibraryCompatGradle_4_1.groovy b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/android/compat/gradle4_1/AndroidLibraryCompatGradle_4_1.groovy new file mode 100644 index 0000000..a337fb1 --- /dev/null +++ b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/android/compat/gradle4_1/AndroidLibraryCompatGradle_4_1.groovy @@ -0,0 +1,24 @@ +package com.novoda.release.internal.android.compat.gradle4_1 + + +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext + +import javax.inject.Inject + +class AndroidLibraryCompatGradle_4_1 implements SoftwareComponentInternal { + + @Inject + AndroidLibraryCompatGradle_4_1() { + } + + @Override + String getName() { + return 'android' + } + + @Override + Set getUsages() { + return Collections.emptySet() + } +} From b180d9f416399d5dd810ea94f9af206b6e3300cd Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 11:03:43 +0100 Subject: [PATCH 54/73] Rename settings file in compat module --- plugin/compat/gradle-4_1/{setting.gradle => settings.gradle} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugin/compat/gradle-4_1/{setting.gradle => settings.gradle} (100%) diff --git a/plugin/compat/gradle-4_1/setting.gradle b/plugin/compat/gradle-4_1/settings.gradle similarity index 100% rename from plugin/compat/gradle-4_1/setting.gradle rename to plugin/compat/gradle-4_1/settings.gradle From 9b0fad71c3f16322d3099eced57ec4d093c71150 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 11:04:08 +0100 Subject: [PATCH 55/73] Add missing Gradle Wrapper for Gradle 4.1 in compat module --- plugin/compat/gradle-4_1/gradlew | 172 +++++++++++++++++++++++++++ plugin/compat/gradle-4_1/gradlew.bat | 84 +++++++++++++ 2 files changed, 256 insertions(+) create mode 100755 plugin/compat/gradle-4_1/gradlew create mode 100644 plugin/compat/gradle-4_1/gradlew.bat diff --git a/plugin/compat/gradle-4_1/gradlew b/plugin/compat/gradle-4_1/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/plugin/compat/gradle-4_1/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +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="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/plugin/compat/gradle-4_1/gradlew.bat b/plugin/compat/gradle-4_1/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/plugin/compat/gradle-4_1/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +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= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From bad11d7522a591969b6a3ce9cbf091f89e7963d7 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 11:11:09 +0100 Subject: [PATCH 56/73] Copy implementation of existing AndroidSoftwareComponent to compat module --- .../AndroidLibraryCompatGradle_4_1.groovy | 24 ----- ...dSoftwareComponentCompat_Gradle_4_1.groovy | 100 ++++++++++++++++++ 2 files changed, 100 insertions(+), 24 deletions(-) delete mode 100644 plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/android/compat/gradle4_1/AndroidLibraryCompatGradle_4_1.groovy create mode 100644 plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy diff --git a/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/android/compat/gradle4_1/AndroidLibraryCompatGradle_4_1.groovy b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/android/compat/gradle4_1/AndroidLibraryCompatGradle_4_1.groovy deleted file mode 100644 index a337fb1..0000000 --- a/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/android/compat/gradle4_1/AndroidLibraryCompatGradle_4_1.groovy +++ /dev/null @@ -1,24 +0,0 @@ -package com.novoda.release.internal.android.compat.gradle4_1 - - -import org.gradle.api.internal.component.SoftwareComponentInternal -import org.gradle.api.internal.component.UsageContext - -import javax.inject.Inject - -class AndroidLibraryCompatGradle_4_1 implements SoftwareComponentInternal { - - @Inject - AndroidLibraryCompatGradle_4_1() { - } - - @Override - String getName() { - return 'android' - } - - @Override - Set getUsages() { - return Collections.emptySet() - } -} diff --git a/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy new file mode 100644 index 0000000..b618853 --- /dev/null +++ b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy @@ -0,0 +1,100 @@ +package com.novoda.release.internal.compat.gradle4_1 + +import org.gradle.api.artifacts.ConfigurationContainer +import org.gradle.api.artifacts.DependencySet +import org.gradle.api.artifacts.ModuleDependency +import org.gradle.api.artifacts.PublishArtifact +import org.gradle.api.attributes.Usage +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext +import org.gradle.api.model.ObjectFactory +import org.gradle.util.GradleVersion + +class AndroidSoftwareComponentCompat_Gradle_4_1 implements SoftwareComponentInternal { + + private final Set usageContexts + private final ConfigurationContainer configurations + + AndroidSoftwareComponentCompat_Gradle_4_1(ObjectFactory objectFactory, ConfigurationContainer configurations) { + this.configurations = configurations + this.usageContexts = generateUsageContexts(objectFactory).asImmutable() + } + + // Using the new Usage in 4.1 will make the plugin crash + // as comparing logic is still using the old Usage. + // For more details: https://github.com/novoda/bintray-release/pull/147 + private Set generateUsageContexts(ObjectFactory objectFactory) { + def isNewerThan4_1 = GradleVersion.current() > GradleVersion.version("4.1") + Usage runtime = objectFactory.named(Usage.class, isNewerThan4_1 ? Usage.JAVA_RUNTIME : "for runtime") + Usage compile = objectFactory.named(Usage.class, isNewerThan4_1 ? Usage.JAVA_API : "for compile") + RuntimeUsageContext runtimeUsage = new RuntimeUsageContext(runtime) + CompileUsageContext compileUsage = new CompileUsageContext(compile) + return [runtimeUsage, compileUsage] as Set + } + + @Override + Set getUsages() { + return usageContexts + } + + @Override + String getName() { + return 'android' + } + + private class RuntimeUsageContext implements UsageContext { + + private final Usage usage + private DependencySet dependencies + + RuntimeUsageContext(Usage usage) { + this.usage = usage + } + + @Override + Usage getUsage() { + return usage + } + + @Override + Set getArtifacts() { + return Collections.emptySet() + } + + @Override + Set getDependencies() { + if (dependencies == null) { + dependencies = configurations.getByName('implementation').getAllDependencies() + } + return dependencies.withType(ModuleDependency.class) + } + } + + private class CompileUsageContext implements UsageContext { + + private final Usage usage + private DependencySet dependencies + + CompileUsageContext(Usage usage) { + this.usage = usage + } + + @Override + Usage getUsage() { + return usage + } + + @Override + Set getArtifacts() { + return Collections.emptySet() + } + + @Override + Set getDependencies() { + if (dependencies == null) { + dependencies = (configurations.findByName('api') ?: configurations.getByName('compile')).getAllDependencies() + } + return dependencies.withType(ModuleDependency.class) + } + } +} From 8766ca044a178cc64dee28311d243be7ea376626 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 11:14:05 +0100 Subject: [PATCH 57/73] Add setup to automatically compile and add compat classes to core module classpath and jar --- plugin/core/build.gradle | 1 + plugin/core/compat.gradle | 56 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 plugin/core/compat.gradle diff --git a/plugin/core/build.gradle b/plugin/core/build.gradle index 015878d..feb3a87 100644 --- a/plugin/core/build.gradle +++ b/plugin/core/build.gradle @@ -8,6 +8,7 @@ version = '0.8.1' apply plugin: 'com.novoda.bintray-release' apply from: 'publish.gradle' +apply from: 'compat.gradle' dependencies { implementation 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' diff --git a/plugin/core/compat.gradle b/plugin/core/compat.gradle new file mode 100644 index 0000000..94b390e --- /dev/null +++ b/plugin/core/compat.gradle @@ -0,0 +1,56 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +String gw = Os.isFamily(Os.FAMILY_WINDOWS) ? 'gradlew.bat' : './gradlew' +String compatClassesDestination = 'build/compat/classes' +TaskContainer taskContainer = project.tasks + +File[] compatModulesDirs = rootProject.file('plugin/compat').listFiles(new FilenameFilter() { + + /** + * We want to collect all the folders inside plugin/compat that are also standalone Gradle projects, + * hence we look for their settings.gradle + */ + @Override + boolean accept(File dir, String name) { + return new File(dir, "$name/settings.gradle").exists() + } +}) + +Task compatClassesTask = taskContainer.create(name: 'releasePluginCompatClasses', type: DefaultTask) { + outputs.dir(compatClassesDestination) +} + +compatModulesDirs.each { File moduleDir -> + + def moduleName = moduleDir.name + + def cleanModuleTask = taskContainer.create(name: "clean${moduleName.capitalize()}", type: Delete) { + delete new File(moduleDir, 'build') + } + taskContainer.clean.dependsOn cleanModuleTask + + def compileModuleTask = taskContainer.create(name: "compile${moduleName.capitalize()}Classes", type: Exec) { + outputs.dir(new File(moduleDir, 'build')) + workingDir moduleDir + commandLine gw, 'clean', 'build' + } + + def copyClassesTask = taskContainer.create(name: "copy${moduleName.capitalize()}Classes", type: Copy) { + from new File(moduleDir, 'build/classes/groovy/main') + into compatClassesDestination + dependsOn compileModuleTask + } + compatClassesTask.dependsOn copyClassesTask +} + +sourceSets { + main { + groovy { + output.dir(project.files(compatClassesDestination)) + } + } +} + +dependencies { + compileOnly files(compatClassesTask) +} From 2a4342d4253e5f79b1d5a23dc21f3f0f33c43b90 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sun, 28 Oct 2018 11:14:44 +0100 Subject: [PATCH 58/73] Use reflection to initialise compat implementation of SoftwareComponent compiled with different version of Gradle --- .../internal/AndroidAttachments.groovy | 8 +- .../internal/AndroidSoftwareComponent.groovy | 100 ------------------ 2 files changed, 6 insertions(+), 102 deletions(-) delete mode 100644 plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidSoftwareComponent.groovy diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy index b14ee3d..9216db0 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy @@ -3,10 +3,13 @@ package com.novoda.gradle.release.internal import com.novoda.gradle.release.MavenPublicationAttachments import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.component.SoftwareComponent import org.gradle.api.tasks.javadoc.Javadoc class AndroidAttachments extends MavenPublicationAttachments { + private static final String ANDROID_SOFTWARE_COMPONENT_COMPAT_4_1 = 'com.novoda.release.internal.compat.gradle4_1.AndroidSoftwareComponentCompat_Gradle_4_1' + AndroidAttachments(String publicationName, Project project, def variant) { super(androidComponentFrom(project), androidSourcesJarTask(project, publicationName, variant), @@ -14,8 +17,9 @@ class AndroidAttachments extends MavenPublicationAttachments { androidArchivePath(variant)) } - private static AndroidSoftwareComponent androidComponentFrom(Project project) { - return new AndroidSoftwareComponent(project.objects, project.configurations) + private static SoftwareComponent androidComponentFrom(Project project) { + def clazz = this.classLoader.loadClass(ANDROID_SOFTWARE_COMPONENT_COMPAT_4_1) + return clazz.newInstance(project.objects, project.configurations) as SoftwareComponent } private static Task androidSourcesJarTask(Project project, String publicationName, def variant) { diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidSoftwareComponent.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidSoftwareComponent.groovy deleted file mode 100644 index a9ab505..0000000 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidSoftwareComponent.groovy +++ /dev/null @@ -1,100 +0,0 @@ -package com.novoda.gradle.release.internal - -import org.gradle.api.artifacts.ConfigurationContainer -import org.gradle.api.artifacts.DependencySet -import org.gradle.api.artifacts.ModuleDependency -import org.gradle.api.artifacts.PublishArtifact -import org.gradle.api.attributes.Usage -import org.gradle.api.internal.component.SoftwareComponentInternal -import org.gradle.api.internal.component.UsageContext -import org.gradle.api.model.ObjectFactory -import org.gradle.util.GradleVersion - -class AndroidSoftwareComponent implements SoftwareComponentInternal { - - private final Set usageContexts - private final ConfigurationContainer configurations - - AndroidSoftwareComponent(ObjectFactory objectFactory, ConfigurationContainer configurations) { - this.configurations = configurations - this.usageContexts = generateUsageContexts(objectFactory).asImmutable() - } - - // Using the new Usage in 4.1 will make the plugin crash - // as comparing logic is still using the old Usage. - // For more details: https://github.com/novoda/bintray-release/pull/147 - private Set generateUsageContexts(ObjectFactory objectFactory) { - def isNewerThan4_1 = GradleVersion.current() > GradleVersion.version("4.1") - Usage runtime = objectFactory.named(Usage.class, isNewerThan4_1 ? Usage.JAVA_RUNTIME : "for runtime") - Usage compile = objectFactory.named(Usage.class, isNewerThan4_1 ? Usage.JAVA_API : "for compile") - RuntimeUsageContext runtimeUsage = new RuntimeUsageContext(runtime) - CompileUsageContext compileUsage = new CompileUsageContext(compile) - return [runtimeUsage, compileUsage] as Set - } - - @Override - Set getUsages() { - return usageContexts - } - - @Override - String getName() { - return 'android' - } - - private class RuntimeUsageContext implements UsageContext { - - private final Usage usage - private DependencySet dependencies - - RuntimeUsageContext(Usage usage) { - this.usage = usage - } - - @Override - Usage getUsage() { - return usage - } - - @Override - Set getArtifacts() { - return Collections.emptySet() - } - - @Override - Set getDependencies() { - if (dependencies == null) { - dependencies = configurations.getByName('implementation').getAllDependencies() - } - return dependencies.withType(ModuleDependency.class) - } - } - - private class CompileUsageContext implements UsageContext { - - private final Usage usage - private DependencySet dependencies - - CompileUsageContext(Usage usage) { - this.usage = usage - } - - @Override - Usage getUsage() { - return usage - } - - @Override - Set getArtifacts() { - return Collections.emptySet() - } - - @Override - Set getDependencies() { - if (dependencies == null) { - dependencies = (configurations.findByName('api') ?: configurations.getByName('compile')).getAllDependencies() - } - return dependencies.withType(ModuleDependency.class) - } - } -} From 3950c39a0a646cd8b97869ac2edb4dc69e8f19a7 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Tue, 30 Oct 2018 22:34:38 +0100 Subject: [PATCH 59/73] Add javadoc to document original reference for implementation --- .../AndroidSoftwareComponentCompat_Gradle_4_1.groovy | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy index b618853..e187e04 100644 --- a/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy +++ b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy @@ -10,6 +10,10 @@ import org.gradle.api.internal.component.UsageContext import org.gradle.api.model.ObjectFactory import org.gradle.util.GradleVersion +/** + * This implementation of {@code SoftwareComponentInternal} is heavily inspired by {@code JavaLibrary}, + * see: https://github.com/gradle/gradle/blob/v4.1.0/subprojects/plugins/src/main/java/org/gradle/api/internal/java/JavaLibrary.java + */ class AndroidSoftwareComponentCompat_Gradle_4_1 implements SoftwareComponentInternal { private final Set usageContexts From b7d583837ce9c577272e82aef3ff929c24162b90 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Tue, 30 Oct 2018 22:43:08 +0100 Subject: [PATCH 60/73] Use implementation configuration instead of compile as we are using Gradle 4.1+ --- plugin/compat/common.gradle | 2 +- plugin/core/build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugin/compat/common.gradle b/plugin/compat/common.gradle index 39a68b1..ac09114 100644 --- a/plugin/compat/common.gradle +++ b/plugin/compat/common.gradle @@ -7,5 +7,5 @@ buildscript { apply plugin: 'groovy' dependencies { - compile gradleApi() + implementation gradleApi() } diff --git a/plugin/core/build.gradle b/plugin/core/build.gradle index feb3a87..3362ff8 100644 --- a/plugin/core/build.gradle +++ b/plugin/core/build.gradle @@ -13,8 +13,8 @@ apply from: 'compat.gradle' dependencies { implementation 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' - testCompile 'junit:junit:4.12' - testCompile 'com.google.truth:truth:0.42' + testImplementation 'junit:junit:4.12' + testImplementation 'com.google.truth:truth:0.42' testRuntime files(pluginUnderTestMetadata) } From 4ec09bc5412f93dc07ed94a3e3ccfe245fee2ea2 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 3 Nov 2018 11:23:47 +0100 Subject: [PATCH 61/73] Introduce model for generated Pom file --- .../novoda/gradle/test/GeneratedPom.groovy | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 plugin/core/src/test/groovy/com/novoda/gradle/test/GeneratedPom.groovy diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/GeneratedPom.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/GeneratedPom.groovy new file mode 100644 index 0000000..458f20c --- /dev/null +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/GeneratedPom.groovy @@ -0,0 +1,22 @@ +package com.novoda.gradle.test + +class GeneratedPom { + private final def nodes + + static GeneratedPom from(File file) { + def nodes = new XmlSlurper().parse(file) + return new GeneratedPom(nodes) + } + + private GeneratedPom(nodes) { + this.nodes = nodes + } + + def getDependencies() { + return nodes.dependencies.dependency + } + + def dependency(String artifactId) { + return dependencies.find { it.artifactId == artifactId } + } +} From 395a5374e6db4f6505fea0d164d8d76881834fb1 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 5 Nov 2018 19:42:48 +0100 Subject: [PATCH 62/73] Expose GradleVersion property from BuildConfiguration --- .../gradle/release/ReleasePluginTest.groovy | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy index 1e9fead..db7b3a9 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy @@ -8,6 +8,7 @@ import org.gradle.api.file.ConfigurableFileTree import org.gradle.api.file.FileTree import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome +import org.gradle.util.GradleVersion import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -17,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat @RunWith(Parameterized.class) class ReleasePluginTest { + private static final GradleVersion GRADLE_4_1 = GradleVersion.version('4.1') private static final String BASE_UPLOAD_PATH = 'https://api.bintray.com/content/novoda/maven/test/1.0/com/novoda/test/1.0/test-1.0' private static final String SOURCES_UPLOAD_PATH = "$BASE_UPLOAD_PATH-sources.jar" private static final String JAVADOC_UPLOAD_PATH = "$BASE_UPLOAD_PATH-javadoc.jar" @@ -153,33 +155,35 @@ class ReleasePluginTest { } private static class BuildConfiguration { - final String gradleVersion + final GradleVersion gradleVersion final TestProject testProject final boolean expectedBuildSuccess static BuildConfiguration forAndroid(String gradleVersion, boolean expectedBuildSuccess) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } + def buildGradleVersion = GradleVersion.version(gradleVersion) def buildScript = GradleScriptTemplates.forAndroidProject() def testProject = TestProject.newAndroidProject(buildScript, additionalRunnerConfig) - return new BuildConfiguration(gradleVersion, testProject, expectedBuildSuccess) + return new BuildConfiguration(buildGradleVersion, testProject, expectedBuildSuccess) } static BuildConfiguration forJava(String gradleVersion, boolean expectedBuildSuccess) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } + def buildGradleVersion = GradleVersion.version(gradleVersion) def buildScript = GradleScriptTemplates.forJavaProject() def testProject = TestProject.newJavaProject(buildScript, additionalRunnerConfig) - return new BuildConfiguration(gradleVersion, testProject, expectedBuildSuccess) + return new BuildConfiguration(buildGradleVersion, testProject, expectedBuildSuccess) } - private BuildConfiguration(String gradleVersion, TestProject testProject, boolean expectedBuildSuccess) { - this.gradleVersion = gradleVersion + private BuildConfiguration(GradleVersion buildGradleVersion, TestProject testProject, boolean expectedBuildSuccess) { + this.gradleVersion = buildGradleVersion this.testProject = testProject this.expectedBuildSuccess = expectedBuildSuccess } @Override String toString() { - return "${testProject.projectType.capitalize()} project with Gradle $gradleVersion" + return "${testProject.projectType.capitalize()} project with $gradleVersion" } String getKey() { From 96d73d2f2ac574ab5413f6963430687dfd3a8e02 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 5 Nov 2018 20:46:42 +0100 Subject: [PATCH 63/73] Add missing test around generated pom content --- .../gradle/release/ReleasePluginTest.groovy | 89 +++++++++++++++---- 1 file changed, 70 insertions(+), 19 deletions(-) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy index db7b3a9..1ee27c7 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy @@ -1,5 +1,8 @@ package com.novoda.gradle.release + +import com.google.common.truth.TruthJUnit +import com.novoda.gradle.test.GeneratedPom import com.novoda.gradle.test.GradleBuildResult import com.novoda.gradle.test.GradleScriptTemplates import com.novoda.gradle.test.TestProject @@ -9,6 +12,7 @@ import org.gradle.api.file.FileTree import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.TaskOutcome import org.gradle.util.GradleVersion +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized @@ -46,46 +50,59 @@ class ReleasePluginTest { ] } - private final TestProject testProject private final BuildConfiguration configuration ReleasePluginTest(BuildConfiguration configuration) { this.configuration = configuration - this.testProject = configuration.testProject } - private GradleBuildResult getResult() { + @Before + void setUp() throws Exception { if (RESULTS[configuration.key] == null) { - testProject.init("${this.class.canonicalName}/${configuration}/test") - RESULTS[configuration.key] = testProject.execute('clean', 'build', 'bintrayUpload', '-PbintrayKey=key', '-PbintrayUser=user', '--stacktrace') + configuration.testProject.init("${this.class.canonicalName}/${configuration}/test") + RESULTS[configuration.key] = configuration.testProject.execute('clean', 'build', 'bintrayUpload', '-PbintrayKey=key', '-PbintrayUser=user', '--stacktrace') } - return RESULTS[configuration.key] } @Test void shouldBuildLibrary() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) GradleTruth.assertThat(result.task(':build')).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldGeneratePomFile() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) GradleTruth.assertThat(result.task(configuration.generatePomTaskName)).hasOutcome(TaskOutcome.SUCCESS) } + @Test + void shouldProvideCompileScopeDependenciesInGeneratedPomFile() { + skipTestWhen(configuration.expectedBuildFailure) + + assertThat(generatedPom.dependency('rxjava').scope).isEqualTo('compile') + } + + @Test + void shouldProvideRuntimeScopeDependenciesInGeneratedPomFile() { + skipTestWhen(configuration.expectedBuildFailure) + skipTestWhen(configuration.gradleVersion < GRADLE_4_1) + + assertThat(generatedPom.dependency('okio').scope).isEqualTo('runtime') + } + @Test void shouldGenerateJavadocs() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) GradleTruth.assertThat(result.task(configuration.generateJavadocsTaskName)).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldPackageAllGeneratedJavadocs() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) GradleTruth.assertThat(result.task(configuration.packageJavadocsTaskName)).hasOutcome(TaskOutcome.SUCCESS) @@ -97,7 +114,7 @@ class ReleasePluginTest { @Test void shouldPackageAllSources() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) GradleTruth.assertThat(result.task(configuration.packageSourcesTaskName)).hasOutcome(TaskOutcome.SUCCESS) @@ -109,42 +126,42 @@ class ReleasePluginTest { @Test void shouldPublishToMavenLocal() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) GradleTruth.assertThat(result.task(configuration.publishToMavenLocalTaskName)).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldRunUploadTask() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) GradleTruth.assertThat(result.task(":bintrayUpload")).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldUploadSourcesJar() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) assertThat(result.output).contains(SOURCES_UPLOAD_PATH) } @Test void shouldUploadJavadocJar() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) assertThat(result.output).contains(JAVADOC_UPLOAD_PATH) } @Test void shouldUploadPomFile() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) assertThat(result.output).contains(POM_UPLOAD_PATH) } @Test void shouldUploadLibraryArtifact() { - GradleTruth.assumeThat(configuration.expectedBuildSuccess).isTrue() + skipTestWhen(configuration.expectedBuildFailure) assertThat(result.output).contains(configuration.libraryUploadPath) } @@ -154,6 +171,23 @@ class ReleasePluginTest { assertThat(result.success).isEqualTo(configuration.expectedBuildSuccess) } + private GradleBuildResult getResult() { + return RESULTS[configuration.key] + } + + private TestProject getTestProject() { + return configuration.testProject + } + + private GeneratedPom getGeneratedPom() { + File pomFile = new File(testProject.projectDir, "/build/publications/$configuration.publicationName/pom-default.xml") + return GeneratedPom.from(pomFile) + } + + private static void skipTestWhen(boolean condition) { + TruthJUnit.assume().that(condition).isFalse() + } + private static class BuildConfiguration { final GradleVersion gradleVersion final TestProject testProject @@ -162,7 +196,7 @@ class ReleasePluginTest { static BuildConfiguration forAndroid(String gradleVersion, boolean expectedBuildSuccess) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } def buildGradleVersion = GradleVersion.version(gradleVersion) - def buildScript = GradleScriptTemplates.forAndroidProject() + def buildScript = addDependenciesTo(GradleScriptTemplates.forAndroidProject(), buildGradleVersion) def testProject = TestProject.newAndroidProject(buildScript, additionalRunnerConfig) return new BuildConfiguration(buildGradleVersion, testProject, expectedBuildSuccess) } @@ -170,11 +204,24 @@ class ReleasePluginTest { static BuildConfiguration forJava(String gradleVersion, boolean expectedBuildSuccess) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } def buildGradleVersion = GradleVersion.version(gradleVersion) - def buildScript = GradleScriptTemplates.forJavaProject() + def buildScript = addDependenciesTo(GradleScriptTemplates.forJavaProject(), buildGradleVersion) def testProject = TestProject.newJavaProject(buildScript, additionalRunnerConfig) return new BuildConfiguration(buildGradleVersion, testProject, expectedBuildSuccess) } + private static String addDependenciesTo(String buildscript, GradleVersion gradleVersion) { + String compileScopeDependency = "${gradleVersion < GRADLE_4_1 ? 'compile' : 'api'} 'io.reactivex.rxjava2:rxjava:2.2.0'" + String runtimeScopeDependency = gradleVersion < GRADLE_4_1 ? '' : 'implementation \'com.squareup.okio:okio:2.1.0\'' + return """ + $buildscript + + dependencies { + $compileScopeDependency + $runtimeScopeDependency + } + """.stripIndent() + } + private BuildConfiguration(GradleVersion buildGradleVersion, TestProject testProject, boolean expectedBuildSuccess) { this.gradleVersion = buildGradleVersion this.testProject = testProject @@ -221,5 +268,9 @@ class ReleasePluginTest { String getLibraryUploadPath() { return isAndroid() ? AAR_UPLOAD_PATH : JAR_UPLOAD_PATH } + + boolean getExpectedBuildFailure() { + return !expectedBuildSuccess + } } } From 5dff50b80d905d3870ac87324ce54d29ac9aad16 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 5 Nov 2018 20:46:51 +0100 Subject: [PATCH 64/73] Remove redundant functional test --- ...eratePomFileForMavenPublicationTest.groovy | 93 ------------------- 1 file changed, 93 deletions(-) delete mode 100644 plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy deleted file mode 100644 index 38e80fb..0000000 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/GeneratePomFileForMavenPublicationTest.groovy +++ /dev/null @@ -1,93 +0,0 @@ -package com.novoda.gradle.release - -import com.novoda.gradle.test.GradleScriptTemplates -import com.novoda.gradle.test.TestProject -import com.novoda.gradle.truth.GradleTruth -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized - -import static com.google.common.truth.Truth.assertThat -import static org.gradle.testkit.runner.TaskOutcome.SUCCESS - -@RunWith(Parameterized.class) -class GeneratePomFileForMavenPublicationTest { - - @Parameterized.Parameters(name = "{index}: test POM descriptor for {0}") - static Collection gradleVersionExpectedOutcome() { - return [Parameter.forJavaProject(), Parameter.forAndroidProject()] - } - - @Rule - public final TestProject testProject - - private final String publicationName - - GeneratePomFileForMavenPublicationTest(Parameter parameter) { - this.testProject = parameter.testProject - this.publicationName = parameter.publicationName - } - - @Test - void shouldContainAllNeededDependenciesInGeneratePomTask() { - def generatingTaskName = ":generatePomFileFor${publicationName.capitalize()}Publication" - - def result = testProject.execute(generatingTaskName, '--stacktrace') - - GradleTruth.assertThat(result.task(generatingTaskName)).hasOutcome(SUCCESS) - assertThat(dependencyScopeFor('hello')).isEqualTo('compile') - assertThat(dependencyScopeFor('haha')).isEqualTo('compile') - assertThat(dependencyScopeFor('world')).isEqualTo('runtime') - } - - private def dependencyScopeFor(String artifactId) { - return dependenciesFromPOM().find { dep -> dep.artifactId == artifactId }.scope - } - - private def dependenciesFromPOM() { - File pomFile = new File(testProject.projectDir, "/build/publications/$publicationName/pom-default.xml") - def nodes = new XmlSlurper().parse(pomFile) - return nodes.dependencies.dependency - } - - private static class Parameter { - - static Parameter forJavaProject() { - return new Parameter( - TestProject.newJavaProject(templateFrom(GradleScriptTemplates.forJavaProject())), - 'maven') - } - - static Parameter forAndroidProject() { - return new Parameter( - TestProject.newAndroidProject(templateFrom(GradleScriptTemplates.forAndroidProject())), - 'release') - } - - private static final String templateFrom(String baseTemplate) { - return """ - $baseTemplate - - dependencies { - compile 'com.abc:hello:1.0.0' - implementation 'com.xyz:world:2.0.0' - api 'com.xxx:haha:3.0.0' - } - """.stripIndent() - - } - - final TestProject testProject - final String publicationName - - Parameter(TestProject testProject, String publicationName) { - this.testProject = testProject - this.publicationName = publicationName - } - - String toString() { - return testProject.projectType - } - } -} From 6e71fee33cc9ff99cd0ca149e3930bb69df09270 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 5 Nov 2018 23:33:04 +0100 Subject: [PATCH 65/73] Make plugin compatible with Gradle 4.0 & Android Gradle Plugin 2.3.0 --- ...dSoftwareComponentCompat_Gradle_4_1.groovy | 11 ++- .../internal/AndroidAttachments.groovy | 2 +- .../gradle/release/ReleasePluginTest.groovy | 76 ++++++------------- .../gradle/test/GradleScriptTemplates.groovy | 8 +- 4 files changed, 33 insertions(+), 64 deletions(-) diff --git a/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy index e187e04..74e20f4 100644 --- a/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy +++ b/plugin/compat/gradle-4_1/src/main/groovy/com/novoda/release/internal/compat/gradle4_1/AndroidSoftwareComponentCompat_Gradle_4_1.groovy @@ -49,7 +49,7 @@ class AndroidSoftwareComponentCompat_Gradle_4_1 implements SoftwareComponentInte private class RuntimeUsageContext implements UsageContext { private final Usage usage - private DependencySet dependencies + private Set dependencies RuntimeUsageContext(Usage usage) { this.usage = usage @@ -68,9 +68,14 @@ class AndroidSoftwareComponentCompat_Gradle_4_1 implements SoftwareComponentInte @Override Set getDependencies() { if (dependencies == null) { - dependencies = configurations.getByName('implementation').getAllDependencies() + def implementation = configurations.findByName('implementation') + if (implementation == null) { + dependencies = Collections.emptySet() + } else { + dependencies = implementation.getAllDependencies().withType(ModuleDependency) + } } - return dependencies.withType(ModuleDependency.class) + return dependencies } } diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy index 9216db0..1ddfab6 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy @@ -36,6 +36,6 @@ class AndroidAttachments extends MavenPublicationAttachments { } private static def androidArchivePath(def variant) { - return variant.packageLibrary.archivePath + return variant.outputs[0].packageLibrary } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy index 1ee27c7..e2eeeff 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy @@ -35,18 +35,17 @@ class ReleasePluginTest { @Parameterized.Parameters(name = "{0}") static Collection configurations() { return [ - BuildConfiguration.forAndroid('4.0', false), - BuildConfiguration.forAndroid('4.1', true), - BuildConfiguration.forAndroid('4.2', true), - BuildConfiguration.forAndroid('4.3', true), - BuildConfiguration.forAndroid('4.4', true), - BuildConfiguration.forAndroid('4.5', false), - BuildConfiguration.forJava('4.0', true), - BuildConfiguration.forJava('4.1', true), - BuildConfiguration.forJava('4.2', true), - BuildConfiguration.forJava('4.3', true), - BuildConfiguration.forJava('4.4', true), - BuildConfiguration.forJava('4.5', true) + BuildConfiguration.forAndroid('4.0', '2.3.0'), + BuildConfiguration.forAndroid('4.1', '3.0.0'), + BuildConfiguration.forAndroid('4.2', '3.0.0'), + BuildConfiguration.forAndroid('4.3', '3.0.0'), + BuildConfiguration.forAndroid('4.4', '3.1.0'), + BuildConfiguration.forJava('4.0'), + BuildConfiguration.forJava('4.1'), + BuildConfiguration.forJava('4.2'), + BuildConfiguration.forJava('4.3'), + BuildConfiguration.forJava('4.4'), + BuildConfiguration.forJava('4.5'), ] } @@ -65,29 +64,27 @@ class ReleasePluginTest { } @Test - void shouldBuildLibrary() { - skipTestWhen(configuration.expectedBuildFailure) + void shouldBuildSuccessfully() { + assertThat(result.success).isTrue() + } + @Test + void shouldBuildLibrary() { GradleTruth.assertThat(result.task(':build')).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldGeneratePomFile() { - skipTestWhen(configuration.expectedBuildFailure) - GradleTruth.assertThat(result.task(configuration.generatePomTaskName)).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldProvideCompileScopeDependenciesInGeneratedPomFile() { - skipTestWhen(configuration.expectedBuildFailure) - assertThat(generatedPom.dependency('rxjava').scope).isEqualTo('compile') } @Test void shouldProvideRuntimeScopeDependenciesInGeneratedPomFile() { - skipTestWhen(configuration.expectedBuildFailure) skipTestWhen(configuration.gradleVersion < GRADLE_4_1) assertThat(generatedPom.dependency('okio').scope).isEqualTo('runtime') @@ -95,15 +92,11 @@ class ReleasePluginTest { @Test void shouldGenerateJavadocs() { - skipTestWhen(configuration.expectedBuildFailure) - GradleTruth.assertThat(result.task(configuration.generateJavadocsTaskName)).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldPackageAllGeneratedJavadocs() { - skipTestWhen(configuration.expectedBuildFailure) - GradleTruth.assertThat(result.task(configuration.packageJavadocsTaskName)).hasOutcome(TaskOutcome.SUCCESS) ConfigurableFileTree generatedFiles = testProject.fileTree('build/docs/javadoc') @@ -114,8 +107,6 @@ class ReleasePluginTest { @Test void shouldPackageAllSources() { - skipTestWhen(configuration.expectedBuildFailure) - GradleTruth.assertThat(result.task(configuration.packageSourcesTaskName)).hasOutcome(TaskOutcome.SUCCESS) ConfigurableFileTree sourceFiles = testProject.fileTree('src/main/java') @@ -126,51 +117,34 @@ class ReleasePluginTest { @Test void shouldPublishToMavenLocal() { - skipTestWhen(configuration.expectedBuildFailure) - GradleTruth.assertThat(result.task(configuration.publishToMavenLocalTaskName)).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldRunUploadTask() { - skipTestWhen(configuration.expectedBuildFailure) - GradleTruth.assertThat(result.task(":bintrayUpload")).hasOutcome(TaskOutcome.SUCCESS) } @Test void shouldUploadSourcesJar() { - skipTestWhen(configuration.expectedBuildFailure) - assertThat(result.output).contains(SOURCES_UPLOAD_PATH) } @Test void shouldUploadJavadocJar() { - skipTestWhen(configuration.expectedBuildFailure) - assertThat(result.output).contains(JAVADOC_UPLOAD_PATH) } @Test void shouldUploadPomFile() { - skipTestWhen(configuration.expectedBuildFailure) - assertThat(result.output).contains(POM_UPLOAD_PATH) } @Test void shouldUploadLibraryArtifact() { - skipTestWhen(configuration.expectedBuildFailure) - assertThat(result.output).contains(configuration.libraryUploadPath) } - @Test - void shouldMatchBuildOutcome() { - assertThat(result.success).isEqualTo(configuration.expectedBuildSuccess) - } - private GradleBuildResult getResult() { return RESULTS[configuration.key] } @@ -191,22 +165,21 @@ class ReleasePluginTest { private static class BuildConfiguration { final GradleVersion gradleVersion final TestProject testProject - final boolean expectedBuildSuccess - static BuildConfiguration forAndroid(String gradleVersion, boolean expectedBuildSuccess) { + static BuildConfiguration forAndroid(String gradleVersion, String androidGradlePluginVersion) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } def buildGradleVersion = GradleVersion.version(gradleVersion) - def buildScript = addDependenciesTo(GradleScriptTemplates.forAndroidProject(), buildGradleVersion) + def buildScript = addDependenciesTo(GradleScriptTemplates.forAndroidProject(androidGradlePluginVersion), buildGradleVersion) def testProject = TestProject.newAndroidProject(buildScript, additionalRunnerConfig) - return new BuildConfiguration(buildGradleVersion, testProject, expectedBuildSuccess) + return new BuildConfiguration(buildGradleVersion, testProject) } - static BuildConfiguration forJava(String gradleVersion, boolean expectedBuildSuccess) { + static BuildConfiguration forJava(String gradleVersion) { def additionalRunnerConfig = { GradleRunner runner -> runner.withGradleVersion(gradleVersion) } def buildGradleVersion = GradleVersion.version(gradleVersion) def buildScript = addDependenciesTo(GradleScriptTemplates.forJavaProject(), buildGradleVersion) def testProject = TestProject.newJavaProject(buildScript, additionalRunnerConfig) - return new BuildConfiguration(buildGradleVersion, testProject, expectedBuildSuccess) + return new BuildConfiguration(buildGradleVersion, testProject) } private static String addDependenciesTo(String buildscript, GradleVersion gradleVersion) { @@ -222,10 +195,9 @@ class ReleasePluginTest { """.stripIndent() } - private BuildConfiguration(GradleVersion buildGradleVersion, TestProject testProject, boolean expectedBuildSuccess) { + private BuildConfiguration(GradleVersion buildGradleVersion, TestProject testProject) { this.gradleVersion = buildGradleVersion this.testProject = testProject - this.expectedBuildSuccess = expectedBuildSuccess } @Override @@ -268,9 +240,5 @@ class ReleasePluginTest { String getLibraryUploadPath() { return isAndroid() ? AAR_UPLOAD_PATH : JAR_UPLOAD_PATH } - - boolean getExpectedBuildFailure() { - return !expectedBuildSuccess - } } } diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy index 11c3901..0ad7b64 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/test/GradleScriptTemplates.groovy @@ -27,7 +27,7 @@ class GradleScriptTemplates { """.stripIndent() } - static String forAndroidProject() { + static String forAndroidProject(String androidGradlePluginVersion = '3.0.0') { return """ buildscript { repositories { @@ -35,7 +35,7 @@ class GradleScriptTemplates { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.0.0' + classpath 'com.android.tools.build:gradle:$androidGradlePluginVersion' } } @@ -65,10 +65,6 @@ class GradleScriptTemplates { jcenter() } - dependencies { - implementation "junit:junit:4.12" - } - publish { userOrg = 'novoda' groupId = 'com.novoda' From f122d428f5888aaab69568e7db3aa0cd75f2bfc5 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 5 Nov 2018 20:54:34 +0100 Subject: [PATCH 66/73] Add compat module targeting Gradle 4.5 --- plugin/compat/gradle-4_5/build.gradle | 6 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54708 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + plugin/compat/gradle-4_5/gradlew | 172 ++++++++++++++++++ plugin/compat/gradle-4_5/gradlew.bat | 84 +++++++++ plugin/compat/gradle-4_5/settings.gradle | 1 + 6 files changed, 268 insertions(+) create mode 100644 plugin/compat/gradle-4_5/build.gradle create mode 100644 plugin/compat/gradle-4_5/gradle/wrapper/gradle-wrapper.jar create mode 100644 plugin/compat/gradle-4_5/gradle/wrapper/gradle-wrapper.properties create mode 100755 plugin/compat/gradle-4_5/gradlew create mode 100644 plugin/compat/gradle-4_5/gradlew.bat create mode 100644 plugin/compat/gradle-4_5/settings.gradle diff --git a/plugin/compat/gradle-4_5/build.gradle b/plugin/compat/gradle-4_5/build.gradle new file mode 100644 index 0000000..4147678 --- /dev/null +++ b/plugin/compat/gradle-4_5/build.gradle @@ -0,0 +1,6 @@ +apply from: '../common.gradle' + +task wrapper(type: Wrapper) { + distributionType = Wrapper.DistributionType.ALL + gradleVersion = '4.5' +} diff --git a/plugin/compat/gradle-4_5/gradle/wrapper/gradle-wrapper.jar b/plugin/compat/gradle-4_5/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7a3265ee94c0ab25cf079ac8ccdf87f41d455d42 GIT binary patch literal 54708 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

_x$+l{{cH8$W#CT literal 0 HcmV?d00001 diff --git a/plugin/compat/gradle-4_5/gradle/wrapper/gradle-wrapper.properties b/plugin/compat/gradle-4_5/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..610ad4c --- /dev/null +++ b/plugin/compat/gradle-4_5/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip diff --git a/plugin/compat/gradle-4_5/gradlew b/plugin/compat/gradle-4_5/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/plugin/compat/gradle-4_5/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +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="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/plugin/compat/gradle-4_5/gradlew.bat b/plugin/compat/gradle-4_5/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/plugin/compat/gradle-4_5/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +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= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/plugin/compat/gradle-4_5/settings.gradle b/plugin/compat/gradle-4_5/settings.gradle new file mode 100644 index 0000000..0df55ba --- /dev/null +++ b/plugin/compat/gradle-4_5/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'compat-gradle-4_5' From 135d7138360c91cdf255231c88d364d400a840cb Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 5 Nov 2018 21:43:05 +0100 Subject: [PATCH 67/73] Add SoftwareComponentInternal implementation compatible with Gradle 4.5 API --- ...dSoftwareComponentCompat_Gradle_4_5.groovy | 125 ++++++++++++++++++ .../internal/AndroidAttachments.groovy | 7 + 2 files changed, 132 insertions(+) create mode 100644 plugin/compat/gradle-4_5/src/main/groovy/com/novoda/release/internal/compat/gradle4_5/AndroidSoftwareComponentCompat_Gradle_4_5.groovy diff --git a/plugin/compat/gradle-4_5/src/main/groovy/com/novoda/release/internal/compat/gradle4_5/AndroidSoftwareComponentCompat_Gradle_4_5.groovy b/plugin/compat/gradle-4_5/src/main/groovy/com/novoda/release/internal/compat/gradle4_5/AndroidSoftwareComponentCompat_Gradle_4_5.groovy new file mode 100644 index 0000000..61e2440 --- /dev/null +++ b/plugin/compat/gradle-4_5/src/main/groovy/com/novoda/release/internal/compat/gradle4_5/AndroidSoftwareComponentCompat_Gradle_4_5.groovy @@ -0,0 +1,125 @@ +package com.novoda.release.internal.compat.gradle4_5 + +import org.gradle.api.artifacts.* +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.Usage +import org.gradle.api.internal.attributes.ImmutableAttributes +import org.gradle.api.internal.attributes.ImmutableAttributesFactory +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext +import org.gradle.api.model.ObjectFactory + +import javax.inject.Inject + +class AndroidSoftwareComponentCompat_Gradle_4_5 implements SoftwareComponentInternal { + + private final UsageContext runtimeUsage + private final UsageContext compileUsage + protected final ConfigurationContainer configurations + protected final ObjectFactory objectFactory + protected final ImmutableAttributesFactory attributesFactory + + @Inject + AndroidSoftwareComponentCompat_Gradle_4_5(ObjectFactory objectFactory, ConfigurationContainer configurations, ImmutableAttributesFactory attributesFactory) { + this.configurations = configurations + this.objectFactory = objectFactory + this.attributesFactory = attributesFactory + this.runtimeUsage = new RuntimeUsageContext(Usage.JAVA_RUNTIME) + this.compileUsage = new CompileUsageContext(Usage.JAVA_API) + } + + @Override + Set getUsages() { + return ([runtimeUsage, compileUsage] as Set).asImmutable() + } + + @Override + String getName() { + return 'android' + } + + private abstract class AbstractUsageContext implements UsageContext { + private final Usage usage + private final ImmutableAttributes attributes + + AbstractUsageContext(String usageName) { + this.usage = objectFactory.named(Usage.class, usageName) + this.attributes = attributesFactory.of(Usage.USAGE_ATTRIBUTE, usage) + } + + @Override + AttributeContainer getAttributes() { + return attributes + } + + @Override + Usage getUsage() { + return usage + } + + @Override + Set getArtifacts() { + return Collections.emptySet() + } + } + + private class RuntimeUsageContext extends AbstractUsageContext { + private DependencySet dependencies + + RuntimeUsageContext(String usageName) { + super(usageName) + } + + @Override + String getName() { + return 'runtime' + } + + @Override + Set getDependencies() { + return getRuntimeDependencies().withType(ModuleDependency.class) + } + + @Override + Set getDependencyConstraints() { + return getRuntimeDependencies().withType(DependencyConstraint.class) + } + + private DependencySet getRuntimeDependencies() { + if (dependencies == null) { + dependencies = configurations.getByName('implementation').getIncoming().getDependencies() + } + return dependencies + } + } + + private class CompileUsageContext extends AbstractUsageContext { + private DependencySet dependencies + + CompileUsageContext(String usageName) { + super(usageName); + } + + @Override + String getName() { + return 'api' + } + + @Override + Set getDependencies() { + return getApiDependencies().withType(ModuleDependency.class) + } + + @Override + Set getDependencyConstraints() { + return getApiDependencies().withType(DependencyConstraint.class) + } + + private DependencySet getApiDependencies() { + if (dependencies == null) { + dependencies = configurations.getByName('api').getIncoming().getDependencies() + } + return dependencies + } + } +} diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy index 1ddfab6..f2948f8 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy @@ -5,10 +5,12 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.component.SoftwareComponent import org.gradle.api.tasks.javadoc.Javadoc +import org.gradle.util.GradleVersion class AndroidAttachments extends MavenPublicationAttachments { private static final String ANDROID_SOFTWARE_COMPONENT_COMPAT_4_1 = 'com.novoda.release.internal.compat.gradle4_1.AndroidSoftwareComponentCompat_Gradle_4_1' + private static final String ANDROID_SOFTWARE_COMPONENT_COMPAT_4_5 = 'com.novoda.release.internal.compat.gradle4_5.AndroidSoftwareComponentCompat_Gradle_4_5' AndroidAttachments(String publicationName, Project project, def variant) { super(androidComponentFrom(project), @@ -18,6 +20,11 @@ class AndroidAttachments extends MavenPublicationAttachments { } private static SoftwareComponent androidComponentFrom(Project project) { + def currentGradleVersion = GradleVersion.current() + if (currentGradleVersion >= GradleVersion.version('4.5')) { + def clazz = this.classLoader.loadClass(ANDROID_SOFTWARE_COMPONENT_COMPAT_4_5) + return project.objects.newInstance(clazz) as SoftwareComponent + } def clazz = this.classLoader.loadClass(ANDROID_SOFTWARE_COMPONENT_COMPAT_4_1) return clazz.newInstance(project.objects, project.configurations) as SoftwareComponent } From 46a59608d1828ed22e3908460100850d802bb53d Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 5 Nov 2018 23:36:41 +0100 Subject: [PATCH 68/73] Add missing compatibility tests for Gradle 4.5, 4.6, 4.7 --- .../com/novoda/gradle/release/ReleasePluginTest.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy index e2eeeff..2de8a9a 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy @@ -40,12 +40,17 @@ class ReleasePluginTest { BuildConfiguration.forAndroid('4.2', '3.0.0'), BuildConfiguration.forAndroid('4.3', '3.0.0'), BuildConfiguration.forAndroid('4.4', '3.1.0'), + BuildConfiguration.forAndroid('4.5', '3.1.0'), + BuildConfiguration.forAndroid('4.6', '3.2.0'), + BuildConfiguration.forAndroid('4.7', '3.2.0'), BuildConfiguration.forJava('4.0'), BuildConfiguration.forJava('4.1'), BuildConfiguration.forJava('4.2'), BuildConfiguration.forJava('4.3'), BuildConfiguration.forJava('4.4'), BuildConfiguration.forJava('4.5'), + BuildConfiguration.forJava('4.6'), + BuildConfiguration.forJava('4.7') ] } From cd820229427bd33bd773b4419cb64ad4b4457b0e Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Mon, 5 Nov 2018 23:43:59 +0100 Subject: [PATCH 69/73] Add missing Javadoc --- .../AndroidSoftwareComponentCompat_Gradle_4_5.groovy | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugin/compat/gradle-4_5/src/main/groovy/com/novoda/release/internal/compat/gradle4_5/AndroidSoftwareComponentCompat_Gradle_4_5.groovy b/plugin/compat/gradle-4_5/src/main/groovy/com/novoda/release/internal/compat/gradle4_5/AndroidSoftwareComponentCompat_Gradle_4_5.groovy index 61e2440..fe34e1f 100644 --- a/plugin/compat/gradle-4_5/src/main/groovy/com/novoda/release/internal/compat/gradle4_5/AndroidSoftwareComponentCompat_Gradle_4_5.groovy +++ b/plugin/compat/gradle-4_5/src/main/groovy/com/novoda/release/internal/compat/gradle4_5/AndroidSoftwareComponentCompat_Gradle_4_5.groovy @@ -11,6 +11,10 @@ import org.gradle.api.model.ObjectFactory import javax.inject.Inject +/** + * This implementation of {@code SoftwareComponentInternal} is heavily inspired by {@code JavaLibrary}, + * see: https://github.com/gradle/gradle/blob/v4.5.0/subprojects/plugins/src/main/java/org/gradle/api/internal/java/JavaLibrary.java + */ class AndroidSoftwareComponentCompat_Gradle_4_5 implements SoftwareComponentInternal { private final UsageContext runtimeUsage @@ -97,7 +101,7 @@ class AndroidSoftwareComponentCompat_Gradle_4_5 implements SoftwareComponentInte private DependencySet dependencies CompileUsageContext(String usageName) { - super(usageName); + super(usageName) } @Override From 278b3fd1ca7caa8040346e823eebbf6fce3ec9a3 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Wed, 7 Nov 2018 10:39:07 +0100 Subject: [PATCH 70/73] Add compat project targeting Gradle 4.8 --- plugin/compat/gradle-4_8/build.gradle | 6 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54727 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + plugin/compat/gradle-4_8/gradlew | 172 ++++++++++++++++++ plugin/compat/gradle-4_8/gradlew.bat | 84 +++++++++ plugin/compat/gradle-4_8/settings.gradle | 1 + 6 files changed, 268 insertions(+) create mode 100644 plugin/compat/gradle-4_8/build.gradle create mode 100644 plugin/compat/gradle-4_8/gradle/wrapper/gradle-wrapper.jar create mode 100644 plugin/compat/gradle-4_8/gradle/wrapper/gradle-wrapper.properties create mode 100755 plugin/compat/gradle-4_8/gradlew create mode 100644 plugin/compat/gradle-4_8/gradlew.bat create mode 100644 plugin/compat/gradle-4_8/settings.gradle diff --git a/plugin/compat/gradle-4_8/build.gradle b/plugin/compat/gradle-4_8/build.gradle new file mode 100644 index 0000000..af12ba3 --- /dev/null +++ b/plugin/compat/gradle-4_8/build.gradle @@ -0,0 +1,6 @@ +apply from: '../common.gradle' + +task wrapper(type: Wrapper) { + distributionType = Wrapper.DistributionType.ALL + gradleVersion = '4.8' +} diff --git a/plugin/compat/gradle-4_8/gradle/wrapper/gradle-wrapper.jar b/plugin/compat/gradle-4_8/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..27768f1bbac3ce2d055b20d521f12da78d331e8e GIT binary patch literal 54727 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

i| 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 literal 0 HcmV?d00001 diff --git a/plugin/compat/gradle-4_8/gradle/wrapper/gradle-wrapper.properties b/plugin/compat/gradle-4_8/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9ec837b --- /dev/null +++ b/plugin/compat/gradle-4_8/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip diff --git a/plugin/compat/gradle-4_8/gradlew b/plugin/compat/gradle-4_8/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/plugin/compat/gradle-4_8/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +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="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/plugin/compat/gradle-4_8/gradlew.bat b/plugin/compat/gradle-4_8/gradlew.bat new file mode 100644 index 0000000..e95643d --- /dev/null +++ b/plugin/compat/gradle-4_8/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +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= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/plugin/compat/gradle-4_8/settings.gradle b/plugin/compat/gradle-4_8/settings.gradle new file mode 100644 index 0000000..bac2e66 --- /dev/null +++ b/plugin/compat/gradle-4_8/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'compat-gradle-4_8' From 7ae86196d60e18057d367cea767426c64e36d4eb Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 10 Nov 2018 11:51:18 +0100 Subject: [PATCH 71/73] Add compat SoftwareComponentInternal implementation targeting Gradle 4.8 API --- ...dSoftwareComponentCompat_Gradle_4_8.groovy | 138 ++++++++++++++++++ .../internal/AndroidAttachments.groovy | 5 + .../gradle/release/ReleasePluginTest.groovy | 8 +- 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 plugin/compat/gradle-4_8/src/main/groovy/com/novoda/release/internal/compat/gradle4_8/AndroidSoftwareComponentCompat_Gradle_4_8.groovy diff --git a/plugin/compat/gradle-4_8/src/main/groovy/com/novoda/release/internal/compat/gradle4_8/AndroidSoftwareComponentCompat_Gradle_4_8.groovy b/plugin/compat/gradle-4_8/src/main/groovy/com/novoda/release/internal/compat/gradle4_8/AndroidSoftwareComponentCompat_Gradle_4_8.groovy new file mode 100644 index 0000000..c5c0d3f --- /dev/null +++ b/plugin/compat/gradle-4_8/src/main/groovy/com/novoda/release/internal/compat/gradle4_8/AndroidSoftwareComponentCompat_Gradle_4_8.groovy @@ -0,0 +1,138 @@ +package com.novoda.release.internal.compat.gradle4_8 + +import org.gradle.api.DomainObjectSet +import org.gradle.api.artifacts.* +import org.gradle.api.attributes.AttributeContainer +import org.gradle.api.attributes.Usage +import org.gradle.api.capabilities.Capability +import org.gradle.api.internal.artifacts.configurations.Configurations +import org.gradle.api.internal.attributes.ImmutableAttributes +import org.gradle.api.internal.attributes.ImmutableAttributesFactory +import org.gradle.api.internal.component.SoftwareComponentInternal +import org.gradle.api.internal.component.UsageContext +import org.gradle.api.model.ObjectFactory + +import javax.inject.Inject + +/** + * This implementation of {@code SoftwareComponentInternal} is heavily inspired by {@code JavaLibrary}, + * see: https://github.com/gradle/gradle/blob/v4.8.0/subprojects/plugins/src/main/java/org/gradle/api/internal/java/JavaLibrary.java + */ +class AndroidSoftwareComponentCompat_Gradle_4_8 implements SoftwareComponentInternal { + + private final UsageContext runtimeUsage + private final UsageContext compileUsage + protected final ConfigurationContainer configurations + protected final ObjectFactory objectFactory + protected final ImmutableAttributesFactory attributesFactory + + @Inject + AndroidSoftwareComponentCompat_Gradle_4_8(ObjectFactory objectFactory, ConfigurationContainer configurations, ImmutableAttributesFactory attributesFactory) { + this.configurations = configurations + this.objectFactory = objectFactory + this.attributesFactory = attributesFactory + this.runtimeUsage = createRuntimeUsageContext() + this.compileUsage = createCompileUsageContext() + } + + @Override + Set getUsages() { + return ([runtimeUsage, compileUsage] as Set).asImmutable() + } + + @Override + String getName() { + return 'android' + } + + private abstract class AbstractUsageContext implements UsageContext { + private final Usage usage + private final ImmutableAttributes attributes + + AbstractUsageContext(String usageName) { + this.usage = objectFactory.named(Usage.class, usageName) + this.attributes = attributesFactory.of(Usage.USAGE_ATTRIBUTE, usage) + } + + @Override + AttributeContainer getAttributes() { + return attributes + } + + @Override + Usage getUsage() { + return usage + } + + @Override + Set getArtifacts() { + return Collections.emptySet() + } + } + + private UsageContext createRuntimeUsageContext() { + return new ConfigurationUsageContext(Usage.JAVA_RUNTIME, 'runtime', 'implementation') + } + + private UsageContext createCompileUsageContext() { + return new ConfigurationUsageContext(Usage.JAVA_API, 'api', 'api') + } + + private class ConfigurationUsageContext extends AbstractUsageContext { + private final String name + private final String configurationName + private DomainObjectSet dependencies + private DomainObjectSet dependencyConstraints + private Set capabilities + private Set excludeRules + + + ConfigurationUsageContext(String usageName, String name, String configurationName) { + super(usageName) + this.name = name + this.configurationName = configurationName + } + + @Override + String getName() { + return name + } + + @Override + Set getDependencies() { + if (dependencies == null) { + dependencies = getConfiguration().getIncoming().getDependencies().withType(ModuleDependency.class) + } + return dependencies + } + + @Override + Set getDependencyConstraints() { + if (dependencyConstraints == null) { + dependencyConstraints = getConfiguration().getIncoming().getDependencyConstraints() + } + return dependencyConstraints + } + + @Override + Set getCapabilities() { + if (capabilities == null) { + this.capabilities = Configurations.collectCapabilities(getConfiguration(), Collections.emptySet(), Collections.emptySet()).asImmutable() + } + return capabilities + } + + @Override + Set getGlobalExcludes() { + if (excludeRules == null) { + this.excludeRules = getConfiguration().getExcludeRules().asImmutable() + } + return excludeRules + } + + private Configuration getConfiguration() { + return configurations.getByName(configurationName) + } + } + +} diff --git a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy index f2948f8..73f99e0 100644 --- a/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy +++ b/plugin/core/src/main/groovy/com/novoda/gradle/release/internal/AndroidAttachments.groovy @@ -11,6 +11,7 @@ class AndroidAttachments extends MavenPublicationAttachments { private static final String ANDROID_SOFTWARE_COMPONENT_COMPAT_4_1 = 'com.novoda.release.internal.compat.gradle4_1.AndroidSoftwareComponentCompat_Gradle_4_1' private static final String ANDROID_SOFTWARE_COMPONENT_COMPAT_4_5 = 'com.novoda.release.internal.compat.gradle4_5.AndroidSoftwareComponentCompat_Gradle_4_5' + private static final String ANDROID_SOFTWARE_COMPONENT_COMPAT_4_8 = 'com.novoda.release.internal.compat.gradle4_8.AndroidSoftwareComponentCompat_Gradle_4_8' AndroidAttachments(String publicationName, Project project, def variant) { super(androidComponentFrom(project), @@ -21,6 +22,10 @@ class AndroidAttachments extends MavenPublicationAttachments { private static SoftwareComponent androidComponentFrom(Project project) { def currentGradleVersion = GradleVersion.current() + if (currentGradleVersion >= GradleVersion.version('4.8')) { + def clazz = this.classLoader.loadClass(ANDROID_SOFTWARE_COMPONENT_COMPAT_4_8) + return project.objects.newInstance(clazz) as SoftwareComponent + } if (currentGradleVersion >= GradleVersion.version('4.5')) { def clazz = this.classLoader.loadClass(ANDROID_SOFTWARE_COMPONENT_COMPAT_4_5) return project.objects.newInstance(clazz) as SoftwareComponent diff --git a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy index 2de8a9a..c0baf22 100644 --- a/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy +++ b/plugin/core/src/test/groovy/com/novoda/gradle/release/ReleasePluginTest.groovy @@ -43,6 +43,9 @@ class ReleasePluginTest { BuildConfiguration.forAndroid('4.5', '3.1.0'), BuildConfiguration.forAndroid('4.6', '3.2.0'), BuildConfiguration.forAndroid('4.7', '3.2.0'), + BuildConfiguration.forAndroid('4.8', '3.2.0'), + BuildConfiguration.forAndroid('4.9', '3.2.0'), + BuildConfiguration.forAndroid('4.10', '3.2.0'), BuildConfiguration.forJava('4.0'), BuildConfiguration.forJava('4.1'), BuildConfiguration.forJava('4.2'), @@ -50,7 +53,10 @@ class ReleasePluginTest { BuildConfiguration.forJava('4.4'), BuildConfiguration.forJava('4.5'), BuildConfiguration.forJava('4.6'), - BuildConfiguration.forJava('4.7') + BuildConfiguration.forJava('4.7'), + BuildConfiguration.forJava('4.8'), + BuildConfiguration.forJava('4.9'), + BuildConfiguration.forJava('4.10'), ] } From c55404bda5f55e582d1df5f6d88f6b098ac96b1d Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 10 Nov 2018 12:19:37 +0100 Subject: [PATCH 72/73] Update the README for next release --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9b8d9cc..82ea0dd 100644 --- a/README.md +++ b/README.md @@ -64,11 +64,23 @@ $ ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKe More info on the available properties and other usages in the [Github Wiki](https://github.com/novoda/bintray-release/wiki). ## Gradle compatibility -| bintray-release version | Java Projects | Android Projects | -|-------------------------|---------------|------------------| -| 0.8.* | Gradle 4.0+ | Gradle 4.1+ | -> **Notes:** Currently Gradle 4.5 doesn't work with Android projects +The plugin officially supports only Gradle 4.0+ + +## Snapshots +[![CI status](https://ci.novoda.com/buildStatus/icon?job=bintray-release-snapshot)](https://ci.novoda.com/job/bintray-release-snapshot/lastBuild/console) [![Download from Bintray](https://api.bintray.com/packages/novoda/snapshots/bintray-release/images/download.svg)](https://bintray.com/novoda/snapshots/bintray-release/_latestVersion) + +Snapshot builds from [`develop`](https://github.com/novoda/bintray-release/compare/master...develop) are automatically deployed to a [repository](https://bintray.com/novoda/snapshots/bintray-release/_latestVersion) that is not synced with JCenter. +To consume a snapshot build add an additional maven repo as follows: +``` +repositories { + maven { + url 'https://novoda.bintray.com/snapshots' + } +} +``` + +You can find the latest snapshot version following this [link](https://bintray.com/novoda/snapshots/bintray-release/_latestVersion). ## Links From 645562a123046a7ab534384c480e43998130e7b4 Mon Sep 17 00:00:00 2001 From: Antonio Bertucci Date: Sat, 10 Nov 2018 12:20:39 +0100 Subject: [PATCH 73/73] Update plugin version for next release --- plugin/core/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/core/build.gradle b/plugin/core/build.gradle index 3362ff8..5d68e1c 100644 --- a/plugin/core/build.gradle +++ b/plugin/core/build.gradle @@ -4,7 +4,7 @@ plugins { id 'com.novoda.build-properties' version '0.4.1' } -version = '0.8.1' +version = '0.9' apply plugin: 'com.novoda.bintray-release' apply from: 'publish.gradle'