diff --git a/.gitignore b/.gitignore
index 79716106..49e65be5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,4 +17,4 @@ gradle-app.setting
.idea
*.iml
-local.properties
\ No newline at end of file
+local.properties
diff --git a/.travis.yml b/.travis.yml
index 2cf757af..7526cc51 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,9 +14,14 @@ branches:
except:
- /^v\d/
-#Below skips the installation step completely (https://docs.travis-ci.com/user/customizing-the-build/#Skipping-the-Installation-Step)
install:
- - true
+ - export ANDROID_HOME=~/android-sdk-linux
+ - wget -q "https://dl.google.com/android/repository/sdk-tools-linux-4333796.zip" -O android-sdk-tools.zip
+ - unzip -q android-sdk-tools.zip -d ${ANDROID_HOME}
+ - PATH=${PATH}:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin:${ANDROID_HOME}/platform-tools
+ - yes | sdkmanager --update
+ - yes | sdkmanager --licenses
+ - sdkmanager "tools" "ndk-bundle" "build-tools;29.0.0" "platforms;android-29" > /dev/null
before_script:
- _JAVA_OPTIONS=
diff --git a/build.gradle b/build.gradle
index 3ee95a87..28f6cbef 100644
--- a/build.gradle
+++ b/build.gradle
@@ -31,6 +31,7 @@ allprojects {
repositories {
jcenter()
maven { url "https://plugins.gradle.org/m2/" }
+ maven { url 'https://maven.google.com' }
}
tasks.withType(Test) {
diff --git a/docs/gradle-plugins/android-publish-plugin.md b/docs/gradle-plugins/android-publish-plugin.md
new file mode 100644
index 00000000..8261a710
--- /dev/null
+++ b/docs/gradle-plugins/android-publish-plugin.md
@@ -0,0 +1,27 @@
+### Android libraries support
+
+Android Gradle Plugin `3.6.0-beta05` or newer is required.
+
+Configuration specific to Android library projects (using `com.android.library` plugins):
+
+1. Apply `org.shipkit.android-publish` plugin to each Gradle project (submodule) you want to publish
+(usually they are not the root projects).
+1. Specify `artifactId` in `androidPublish` blocks.
+
+Example:
+
+```Gradle
+apply plugin: 'org.shipkit.bintray'
+apply plugin: 'org.shipkit.android-publish'
+apply plugin: 'com.android.library'
+
+androidPublish {
+ artifactId = 'shipkit-android'
+}
+
+```
+
+Other POM properties which can be set using Gradle API:
+* group id - [Project#group](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:group)
+* name - [Project#archivesBaseName](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:archivesBaseName)
+* description - [Project#description](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html#org.gradle.api.Project:description)
diff --git a/docs/how-shipkit-works.md b/docs/how-shipkit-works.md
index e5fdc5e5..9ad36e61 100644
--- a/docs/how-shipkit-works.md
+++ b/docs/how-shipkit-works.md
@@ -26,6 +26,7 @@ How do we:
- [publish binaries](/docs/features/publishing-binaries.md)
- [publishing binaries using maven-publish plugin](/docs/features/publishing-binaries-using-maven-publish-plugin.md)
- [avoid unnecessary releases](/docs/gradle-plugins/release-needed-plugin.md)
+- [support Android libraries](/docs/gradle-plugins/android-publish-plugin.md)
- [shipping Javadoc](/docs/features/shipping-javadoc.md)
- [automatically include contributors in pom.xml](/docs/features/celebrating-contributors.md)
@@ -41,8 +42,8 @@ script:
- ./gradlew build -s && ./gradlew ciPerformRelease -s
```
-Those lines means the releasing process is two-stage.
-First the `build` Gradle task is executed.
+Those lines means the releasing process is two-stage.
+First the `build` Gradle task is executed.
Shipkit doesn't change there a lot.
More interesting is the second task: `ciPerformRelease`.
This task depends on 3 another tasks: `releaseNeeded`, `ciReleasePrepare` and `performRelease`.
@@ -145,7 +146,7 @@ Text used to create this diagram: https://gist.github.com/mstachniuk/b7cfd3bef9f
| | Info is release needed or not | |------------------------------------| | |
| |<-------------------------------------------| | |
| | | | |
-
+
```
diff --git a/subprojects/shipkit/src/main/groovy/org/shipkit/gradle/configuration/AndroidPublishConfiguration.java b/subprojects/shipkit/src/main/groovy/org/shipkit/gradle/configuration/AndroidPublishConfiguration.java
new file mode 100644
index 00000000..ce72442f
--- /dev/null
+++ b/subprojects/shipkit/src/main/groovy/org/shipkit/gradle/configuration/AndroidPublishConfiguration.java
@@ -0,0 +1,26 @@
+package org.shipkit.gradle.configuration;
+
+import org.gradle.api.GradleException;
+
+public class AndroidPublishConfiguration {
+
+ private String artifactId;
+
+ /**
+ * Artifact id of published AAR
+ * For example: "shipkit-android"
+ */
+ public String getArtifactId() {
+ if (artifactId == null || artifactId.isEmpty()) {
+ throw new GradleException("Please configure artifact id");
+ }
+ return artifactId;
+ }
+
+ /**
+ * See {@link #getArtifactId()} ()}
+ */
+ public void setArtifactId(String artifactId) {
+ this.artifactId = artifactId;
+ }
+}
diff --git a/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/android/AndroidPublishPlugin.java b/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/android/AndroidPublishPlugin.java
new file mode 100644
index 00000000..a1c745f9
--- /dev/null
+++ b/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/android/AndroidPublishPlugin.java
@@ -0,0 +1,82 @@
+package org.shipkit.internal.gradle.android;
+
+import com.jfrog.bintray.gradle.BintrayExtension;
+
+import org.gradle.api.GradleException;
+import org.gradle.api.Plugin;
+import org.gradle.api.Project;
+import org.gradle.api.Task;
+import org.gradle.api.component.SoftwareComponent;
+import org.gradle.api.logging.Logger;
+import org.gradle.api.logging.Logging;
+import org.gradle.api.publish.maven.MavenPublication;
+import org.shipkit.gradle.configuration.AndroidPublishConfiguration;
+import org.shipkit.gradle.configuration.ShipkitConfiguration;
+import org.shipkit.internal.gradle.configuration.ShipkitConfigurationPlugin;
+import org.shipkit.internal.gradle.snapshot.LocalSnapshotPlugin;
+import org.shipkit.internal.gradle.util.GradleDSLHelper;
+import org.shipkit.internal.gradle.util.PomCustomizer;
+
+import static org.shipkit.internal.gradle.configuration.DeferredConfiguration.deferredConfiguration;
+import static org.shipkit.internal.gradle.java.JavaPublishPlugin.MAVEN_LOCAL_TASK;
+import static org.shipkit.internal.gradle.java.JavaPublishPlugin.PUBLICATION_NAME;
+
+/**
+ * Publishing Android libraries using 'maven-publish' plugin.
+ * Intended to be applied in individual Android library submodule.
+ * Applies following plugins and tasks and configures them:
+ *
+ *
+ *
+ * Other features:
+ *
+ * - Configures Gradle's publications to publish Android library
+ * - Configures 'build' task to depend on 'publishJavaLibraryToMavenLocal'
+ * to flesh out publication issues during the build
+ * - Configures 'snapshot' task to depend on 'publishJavaLibraryToMavenLocal'
+ *
+ */
+public class AndroidPublishPlugin implements Plugin {
+
+ private final static Logger LOG = Logging.getLogger(AndroidPublishPlugin.class);
+ private final static String ANDROID_PUBLISH_EXTENSION = "androidPublish";
+
+ public void apply(final Project project) {
+ final AndroidPublishConfiguration androidPublishConfiguration = project.getExtensions().create(ANDROID_PUBLISH_EXTENSION, AndroidPublishConfiguration.class);
+
+ final ShipkitConfiguration conf = project.getPlugins().apply(ShipkitConfigurationPlugin.class).getConfiguration();
+
+ project.getPlugins().apply(LocalSnapshotPlugin.class);
+ Task snapshotTask = project.getTasks().getByName(LocalSnapshotPlugin.SNAPSHOT_TASK);
+ snapshotTask.dependsOn(MAVEN_LOCAL_TASK);
+
+ project.getPlugins().apply("maven-publish");
+
+ BintrayExtension bintray = project.getExtensions().getByType(BintrayExtension.class);
+ bintray.setPublications(PUBLICATION_NAME);
+
+ project.getPlugins().withId("com.android.library", plugin -> {
+ deferredConfiguration(project, () -> {
+ GradleDSLHelper.publications(project, publications -> {
+ MavenPublication p = publications.create(PUBLICATION_NAME, MavenPublication.class, publication -> {
+ publication.setArtifactId(androidPublishConfiguration.getArtifactId());
+
+ SoftwareComponent releaseComponent = project.getComponents().findByName("release");
+ if (releaseComponent == null) {
+ throw new GradleException("'release' component not found in project. " +
+ "Make sure you are using Android Gradle Plugin 3.6.0-beta05 or newer.");
+ }
+ publication.from(releaseComponent);
+ PomCustomizer.customizePom(project, conf, publication);
+ });
+ LOG.info("{} - configured '{}' publication", project.getPath(), p.getArtifactId());
+ });
+ });
+
+ //so that we flesh out problems with maven publication during the build process
+ project.getTasks().getByName("build").dependsOn(MAVEN_LOCAL_TASK);
+ });
+ }
+}
diff --git a/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/java/JavaPublishPlugin.java b/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/java/JavaPublishPlugin.java
index 3466c193..69206b93 100644
--- a/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/java/JavaPublishPlugin.java
+++ b/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/java/JavaPublishPlugin.java
@@ -29,7 +29,7 @@
* Other features:
*
* - Configures Gradle's publications to publish java library
- * - Configures 'build' taks to depend on 'publishJavaLibraryToMavenLocal'
+ *
- Configures 'build' task to depend on 'publishJavaLibraryToMavenLocal'
* to flesh out publication issues during the build
* - Configures 'snapshot' task to depend on 'publishJavaLibraryToMavenLocal'
*
diff --git a/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/util/PomCustomizer.java b/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/util/PomCustomizer.java
index 541adfff..a81d3a54 100644
--- a/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/util/PomCustomizer.java
+++ b/subprojects/shipkit/src/main/groovy/org/shipkit/internal/gradle/util/PomCustomizer.java
@@ -56,7 +56,8 @@ public void execute(XmlProvider xml) {
"\n - Contributors read from GitHub: "
+ StringUtil.join(contributorsFromGitHub.toConfigNotation(), ", "));
- customizePom(xml.asNode(), conf, archivesBaseName, project.getDescription(), contributorsFromGitHub);
+ final boolean isAndroidLibrary = project.getPlugins().hasPlugin("com.android.library");
+ customizePom(xml.asNode(), conf, archivesBaseName, project.getDescription(), contributorsFromGitHub, isAndroidLibrary);
}
});
}
@@ -66,12 +67,14 @@ public void execute(XmlProvider xml) {
*/
static void customizePom(Node root, ShipkitConfiguration conf,
String projectName, String projectDescription,
- ProjectContributorsSet contributorsFromGitHub) {
- //Assumes project has java plugin applied. Pretty safe assumption
+ ProjectContributorsSet contributorsFromGitHub,
+ boolean isAndroidLibrary) {
//TODO: we need to conditionally append nodes because given node may already be on the root (issue 847)
//TODO: all root.appendNode() need to be conditional
root.appendNode("name", projectName);
- if (root.getAt(new QName("packaging")).isEmpty()) {
+
+ //Android library publication uses aar packaging
+ if (!isAndroidLibrary && root.getAt(new QName("packaging")).isEmpty()) {
root.appendNode("packaging", "jar");
}
diff --git a/subprojects/shipkit/src/main/resources/META-INF/gradle-plugins/org.shipkit.android-publish.properties b/subprojects/shipkit/src/main/resources/META-INF/gradle-plugins/org.shipkit.android-publish.properties
new file mode 100644
index 00000000..faf54741
--- /dev/null
+++ b/subprojects/shipkit/src/main/resources/META-INF/gradle-plugins/org.shipkit.android-publish.properties
@@ -0,0 +1 @@
+implementation-class=org.shipkit.internal.gradle.android.AndroidPublishPlugin
diff --git a/subprojects/shipkit/src/test/groovy/org/shipkit/gradle/ShipkitAndroidIntegTest.groovy b/subprojects/shipkit/src/test/groovy/org/shipkit/gradle/ShipkitAndroidIntegTest.groovy
new file mode 100644
index 00000000..847ed1a6
--- /dev/null
+++ b/subprojects/shipkit/src/test/groovy/org/shipkit/gradle/ShipkitAndroidIntegTest.groovy
@@ -0,0 +1,133 @@
+package org.shipkit.gradle
+
+import org.gradle.testkit.runner.BuildResult
+import testutil.GradleSpecification
+
+class ShipkitAndroidIntegTest extends GradleSpecification {
+
+ void setup() {
+ settingsFile << "include 'lib'"
+ newFile('lib/build.gradle') << """
+ apply plugin: 'org.shipkit.bintray'
+ apply plugin: 'org.shipkit.android-publish'
+ androidPublish {
+ artifactId = 'shipkit-android'
+ }
+
+ apply plugin: 'com.android.library'
+ android {
+ compileSdkVersion 29
+ defaultConfig {
+ minSdkVersion 29
+ }
+ }
+ """
+
+ newFile("gradle/shipkit.gradle") << """
+ shipkit {
+ gitHub.readOnlyAuthToken = "foo"
+ gitHub.writeAuthToken = "secret"
+ releaseNotes.file = "CHANGELOG.md"
+ git.user = "shipkit"
+ git.email = "shipkit.org@gmail.com"
+ gitHub.repository = "repo"
+ }
+
+ allprojects {
+ plugins.withId("com.jfrog.bintray") {
+ bintray {
+ user = "szczepiq"
+ key = "secret"
+ }
+ }
+ }
+ """
+ buildFile << """
+ apply plugin: 'org.shipkit.java'
+ buildscript {
+ repositories {
+ google()
+ jcenter()
+ gradlePluginPortal()
+ }
+ }
+ """
+ newFile("src/main/AndroidManifest.xml") << """"""
+ }
+
+ def "all tasks in dry run (gradle #gradleVersionToTest) (AGP #agpVersionToTest)"() {
+ /**
+ * TODO this test is just a starting point we will make it better and create more integration tests
+ * Stuff that we should do:
+ * 1. (Most important) Avoid writing too many integration tests. Most code should be covered by unit tests
+ * (see testing pyramid)
+ * 2. Push out complexity to base class GradleSpecification
+ * so that what remains in the test is the essential part of a tested feature
+ * 3. Add more specific assertions rather than just a list of tasks in dry run mode
+ * 4. Use sensible defaults so that we don't need to specify all configuration in the test
+ * 5. Move integration tests to a separate module
+ * 6. Dependencies are hardcoded between GradleSpecification and build.gradle of release-tools project
+ */
+ given:
+ gradleVersion = gradleVersionToTest
+
+ and:
+ buildFile << """
+ buildscript {
+ dependencies {
+ classpath 'com.android.tools.build:gradle:$agpVersionToTest'
+ }
+ }
+ """
+
+ expect:
+ BuildResult result = pass("performRelease", "-m", "-s")
+ //git push and bintray upload tasks should run as late as possible
+ def output = skippedTaskPathsGradleBugWorkaround(result.output).join("\n")
+ output.startsWith(""":bumpVersionFile
+:identifyGitBranch
+:fetchContributors
+:fetchReleaseNotes
+:updateReleaseNotes
+:gitCommit
+:gitTag
+:gitPush
+:performGitPush
+:updateReleaseNotesOnGitHub
+:lib:preBuild""")
+
+ and:
+ output.endsWith(""":lib:bintrayUpload
+:bintrayPublish
+:performRelease""")
+
+ where:
+ gradleVersionToTest << ["5.6.4", "6.0.1"]
+ and:
+ agpVersionToTest << ["3.6.0-beta05", "3.6.0-rc01"]
+ }
+
+ def "fails on unsupported dependency versions (gradle #gradleVersionToTest) (AGP #agpVersionToTest)"() {
+ given:
+ gradleVersion = gradleVersionToTest
+
+ and:
+ buildFile << """
+ buildscript {
+ dependencies {
+ classpath 'com.android.tools.build:gradle:$agpVersionToTest'
+ }
+ }
+ """
+
+ expect:
+ BuildResult result = fail("performRelease", "-m", "-s")
+ result.output.contains("'release' component not found in project. " +
+ "Make sure you are using Android Gradle Plugin 3.6.0-beta05 or newer.")
+
+ where:
+ gradleVersionToTest << ["5.6.4", "6.0.1"]
+ and:
+ agpVersionToTest << ["3.4.0", "3.5.2"]
+ }
+}
diff --git a/subprojects/shipkit/src/test/groovy/org/shipkit/gradle/configuration/AndroidPublishConfigurationTest.groovy b/subprojects/shipkit/src/test/groovy/org/shipkit/gradle/configuration/AndroidPublishConfigurationTest.groovy
new file mode 100644
index 00000000..51b468e6
--- /dev/null
+++ b/subprojects/shipkit/src/test/groovy/org/shipkit/gradle/configuration/AndroidPublishConfigurationTest.groovy
@@ -0,0 +1,25 @@
+package org.shipkit.gradle.configuration
+
+import org.gradle.api.GradleException
+import spock.lang.Specification
+
+class AndroidPublishConfigurationTest extends Specification {
+
+ def conf = new AndroidPublishConfiguration();
+
+ def "throws when artifact id not configured"() {
+ when:
+ conf.artifactId
+
+ then:
+ thrown(GradleException)
+ }
+
+ def "stores artifact id"() {
+ when:
+ conf.artifactId = "org.shipkit.android"
+
+ then:
+ conf.artifactId == "org.shipkit.android"
+ }
+}
diff --git a/subprojects/shipkit/src/test/groovy/org/shipkit/internal/gradle/util/PomCustomizerTest.groovy b/subprojects/shipkit/src/test/groovy/org/shipkit/internal/gradle/util/PomCustomizerTest.groovy
index bfcca437..45794d65 100644
--- a/subprojects/shipkit/src/test/groovy/org/shipkit/internal/gradle/util/PomCustomizerTest.groovy
+++ b/subprojects/shipkit/src/test/groovy/org/shipkit/internal/gradle/util/PomCustomizerTest.groovy
@@ -35,7 +35,7 @@ class PomCustomizerTest extends Specification {
//wwilk will not be duplicated in developers/contributors
conf.team.contributors = ["mstachniuk:Marcin Stachniuk", "wwilk:Wojtek Wilk"]
- PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet())
+ PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet(), false)
expect:
printXml(node) == """
@@ -98,7 +98,7 @@ class PomCustomizerTest extends Specification {
contributorsSet.addContributor(new DefaultProjectContributor("Wojtek Wilk", "wwilk", "https://github.com/wwilk", 5))
contributorsSet.addContributor(new DefaultProjectContributor("Marcin Stachniuk", "mstachniuk", "https://github.com/mstachniuk", 3))
- PomCustomizer.customizePom(node, conf, "foo", "Foo library", contributorsSet)
+ PomCustomizer.customizePom(node, conf, "foo", "Foo library", contributorsSet, false)
expect:
printXml(node) == """
@@ -157,7 +157,7 @@ class PomCustomizerTest extends Specification {
conf.team.developers = []
conf.team.contributors = []
- PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet())
+ PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet(), false)
expect:
printXml(node) == """
@@ -187,12 +187,46 @@ class PomCustomizerTest extends Specification {
"""
}
+ def "AAR packaging in Android library"() {
+ conf.gitHub.repository = "repo"
+ node.appendNode("packaging", "aar")
+
+ PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet(), true)
+
+ expect:
+ printXml(node) == """
+ aar
+ foo
+ https://github.com/repo
+ Foo library
+
+
+ The MIT License
+ https://github.com/repo/blob/master/LICENSE
+ repo
+
+
+
+ https://github.com/repo.git
+
+
+ https://github.com/repo/issues
+ GitHub issues
+
+
+ https://travis-ci.org/repo
+ TravisCI
+
+
+"""
+ }
+
def "CI management from configuration"() {
conf.gitHub.repository = "repo"
conf.ciManagement.system = "Bitrise"
conf.ciManagement.url = "https://app.bitrise.io/app/slug"
- PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet())
+ PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet(), false)
expect:
printXml(node) == """
@@ -225,7 +259,7 @@ class PomCustomizerTest extends Specification {
def "default CI management"() {
conf.gitHub.repository = "repo"
- PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet())
+ PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet(), false)
expect:
printXml(node) == """
@@ -260,7 +294,7 @@ class PomCustomizerTest extends Specification {
conf.gitHub.repository = "repo"
node.appendNode("packaging", "unbundled");
- PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet())
+ PomCustomizer.customizePom(node, conf, "foo", "Foo library", new DefaultProjectContributorsSet(), false)
expect:
printXml(node) == """
diff --git a/version.properties b/version.properties
index 98011ba7..fa07a47e 100644
--- a/version.properties
+++ b/version.properties
@@ -1,6 +1,6 @@
#Version of the produced binaries. This file is intended to be checked-in.
#It will be automatically bumped by release automation.
-version=2.2.8
+version=2.3.0
#Last previous release version
previousVersion=2.2.7