Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Gradle Plugins

Building Gradle Plugins

Presented at Chicago Roboto on April 21st, 2017

Bryan Sills

April 21, 2017
Tweet

More Decks by Bryan Sills

Other Decks in Technology

Transcript

  1. What Is Gradle? It’s a build system. More specifically, it’s

    the main supported build system for building Android apps.
  2. What Are Gradle Plugins? Small chunks of code that can:

    • Automate simple parts of your build process • Provide the framework to use outside tools in your build • Extract useful information about the build process
  3. buildscript { repositories { maven { url 'https://maven.fabric.io/public' } }

    dependencies { classpath 'io.fabric.tools:gradle:1.+' } } apply plugin: 'com.android.application' apply plugin: 'io.fabric' repositories { maven { url 'https://maven.fabric.io/public' } } dependencies { compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' compile('com.crashlytics.sdk.android:crashlytics:2.6.7@aar') { transitive = true; } } {... more gradle stuff ...} app/build.gradle
  4. Crashlytics Tasks $ ./gradlew assembleDebug crashlyticsUploadDistributionDebug {...} :app:prepareDebugDependencies :app:compileDebugAidl UP-TO-DATE

    :app:compileDebugRenderscript UP-TO-DATE :app:generateDebugBuildConfig UP-TO-DATE :app:mergeDebugShaders UP-TO-DATE :app:compileDebugShaders UP-TO-DATE :app:generateDebugAssets UP-TO-DATE :app:mergeDebugAssets UP-TO-DATE :app:processDebugManifest UP-TO-DATE :app:fabricGenerateResourcesDebug :app:generateDebugResValues UP-TO-DATE :app:generateDebugResources :app:mergeDebugResources :app:processDebugResources :app:generateDebugSources :app:incrementalDebugJavaCompilationSafeguard UP-TO-DATE :app:javaPreCompileDebug :app:compileDebugJavaWithJavac UP-TO-DATE :app:compileDebugNdk UP-TO-DATE :app:compileDebugSources UP-TO-DATE :app:transformClassesWithDexForDebug UP-TO-DATE :app:mergeDebugJniLibFolders UP-TO-DATE :app:transformNativeLibsWithMergeJniLibsForDebug UP-TO-DATE :app:processDebugJavaRes UP-TO-DATE :app:transformResourcesWithMergeJavaResForDebug UP-TO-DATE :app:validateSigningDebug :app:packageDebug :app:assembleDebug :app:crashlyticsUploadDistributionDebug BUILD SUCCESSFUL Total time: 13.37 secs
  5. :crashlyticsUploadDistribution<VARIANT> • Uploads your .apk (and other fun stuff) to

    Crashlytics Beta • <VARIANT> refers to Android’s build variant ◦ Defaults to “Debug” and “Release” • Standalone task
  6. :fabricGenerateResources<VARIANT> • Generates a unique identifier for your build into

    an XML file • Inserted into the middle of the build process
  7. What prompted you to learn about Gradle plugins, Bryan? I

    wanted to see if some JSON files were actually valid JSON.
  8. Context (Not that Context) • My client’s project uses JSON

    files to define the configuration of the app ◦ Cross-platform between iOS and Android • Some of the configurable elements: ◦ Server endpoints (and other various networking pieces) ◦ UI Elements (Individual layouts, Drawer items, colors) ◦ Core features (User signup) • These JSON files come from third-party teams • The people who make these JSON files may not be in technical roles
  9. So what happens... • Build the app • Install on

    my device • Open the app on my phone • Go a few screens to switch JSON configurations FORCE CLOSE...
  10. Still… this seems like a lot of work... Why don’t

    you just use an online JSON formatter? • These JSON files change often • We deal with third-party developers in our codebase • These are dumb, painful mistakes that can be prevented
  11. Why Use Gradle Plugins? Bash still kinda works… • Write

    in Java, Kotlin, or Groovy • You will always have Gradle when building your Android app • Use all the Java libraries and tools in your plugin • Build on top of other Gradle plugins • Integrate into your build
  12. Project Structure There are 3 ways to build your plugin:

    • Directly in the build.gradle file • In the root buildSrc/ folder of your project • In a standalone project
  13. Setting up the plugin’s build.gradle plugins { id "java-gradle-plugin" }

    group "ninja.bryansills" version '0.0.1' gradlePlugin { plugins { jsonVerify { id = "ninja.bryansills.gradle-json-verify" implementationClass = "ninja.bryansills.JsonVerifyPlugin" } } }
  14. plugins { id "java-gradle-plugin" } group "ninja.bryansills" version '0.0.1' gradlePlugin

    { plugins { jsonVerify { id = "ninja.bryansills.gradle-json-verify" implementationClass = "ninja.bryansills.JsonVerifyPlugin" } } } Setting up the plugin’s build.gradle
  15. Create your initial Java Plugin file package ninja.bryansills; import org.gradle.api.Plugin;

    import org.gradle.api.Project; public class JsonVerifyPlugin implements Plugin<Project> { @Override public void apply(Project project) { } }
  16. Why isn’t this working? $ ./gradlew :consumer:dependencies FAILURE: Build failed

    with an exception. * What went wrong: A problem occurred configuring project ':consumer'. > Could not resolve all dependencies for configuration ':consumer:classpath'. > Could not find ninja.bryansills:gradle-json-verify:0.0.1. Searched in the following locations: https://jcenter.bintray.com/ninja/bryansills/gradle-json-verify/0.0.1/gradle-json-verify-0.0.1.pom https://jcenter.bintray.com/ninja/bryansills/gradle-json-verify/0.0.1/gradle-json-verify-0.0.1.jar Required by: project :consumer * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. BUILD FAILED Total time: 2.826 secs
  17. Gradle Lifecycle • There are 3 phases to the Gradle

    lifecycle: ◦ Initialization ◦ Configuration ◦ Execution • All projects need to configured before they are executed… • But the consumer needs a compiled plugin… • So...
  18. The :uploadArchives task • Allows you to build the plugin

    and upload the .jar to a local or remote repository • Solves the configuration phase issue • A little tedious while doing iterative development on the plugin ◦ Ughhhhhhhhhhhhh 2 different commands
  19. Composite Builds settings.gradle rootProject.name = 'gradle-json-verify-project' include ':consumer', ':gradle-json-verify' includeBuild

    './gradle-json-verify' • Fairly new feature in Gradle (Added in late 2016) • A composite build is simply a build that includes other builds • Allows the plugin to be fully built before the consumer needs it
  20. Composite Builds $ ./gradlew :consumer:dependencies [composite-build] Configuring build: /Users/bsills/android/gradle-json-verify :gradle-json-verify:compileJava

    UP-TO-DATE :gradle-json-verify:pluginDescriptors UP-TO-DATE :gradle-json-verify:processResources UP-TO-DATE :gradle-json-verify:classes UP-TO-DATE :gradle-json-verify:jar UP-TO-DATE :consumer:dependencies {...} BUILD SUCCESSFUL Total time: 1.538 secs
  21. Creating a Custom Task public class JsonVerifyPlugin implements Plugin<Project> {

    @Override public void apply(Project project) { Task jsonVerifyTask = project.getTasks().create("jsonVerify", JsonVerifyTask.class); } } public class JsonVerifyTask extends DefaultTask { @TaskAction public void jsonVerify() { System.out.println("We are verifying JSON"); } }
  22. Creating a Custom Task $ ./gradlew :consumer:jsonVerify [composite-build] Configuring build:

    /Users/bsills/android/gradle-json-verify :gradle-json-verify:compileJava UP-TO-DATE :gradle-json-verify:pluginDescriptors UP-TO-DATE :gradle-json-verify:processResources UP-TO-DATE :gradle-json-verify:classes UP-TO-DATE :gradle-json-verify:jar UP-TO-DATE :consumer:jsonVerify We are verifying JSON BUILD SUCCESSFUL Total time: 1.021 secs
  23. Depending On Other Tasks $ ./gradlew :consumer:assembleDebug [composite-build] Configuring build:

    /Users/bsills/android/gradle-json-verify :gradle-json-verify:compileJava :gradle-json-verify:pluginDescriptors UP-TO-DATE :gradle-json-verify:processResources UP-TO-DATE :gradle-json-verify:classes :gradle-json-verify:jar :consumer:preBuild :consumer:preDebugBuild :consumer:checkDebugManifest :consumer:preReleaseBuild {... continued ...}
  24. Depending On Other Tasks public class JsonVerifyPlugin implements Plugin<Project> {

    @Override public void apply(Project project) { Task jsonVerifyTask = project.getTasks().create("jsonVerify", JsonVerifyTask.class); Set<Task> tasks = project.getTasksByName("preBuild", true); tasks.forEach(task -> task.dependsOn(jsonVerifyTask)); } }
  25. Depending On Other Tasks $ ./gradlew :consumer:assembleDebug [composite-build] Configuring build:

    /Users/bsills/android/gradle-json-verify :gradle-json-verify:compileJava :gradle-json-verify:pluginDescriptors UP-TO-DATE :gradle-json-verify:processResources UP-TO-DATE :gradle-json-verify:classes :gradle-json-verify:jar :consumer:jsonVerify We are verifying JSON :consumer:preBuild :consumer:preDebugBuild :consumer:checkDebugManifest :consumer:preReleaseBuild {... continued ...}
  26. Taking Input JsonVerifyTask.java @TaskAction public void jsonVerify() { ConfigurableFileTree configFiles

    = getProject().fileTree(getProject().getProjectDir() + "/json/"); configFiles.forEach(file -> { if (".json".equals(getFileExtension(file))) { verifyJsonFile(file); } }); } {... continued ...}
  27. Verifying Input JsonVerifyTask.java private void verifyJsonFile(File file) { Moshi moshi

    = new Moshi.Builder().build(); JsonAdapter<Map> jsonAdapter = moshi.adapter(Map.class); try { Map jsonMap = jsonAdapter.fromJson(Okio.buffer(Okio.source(file))); } catch (Exception exception) { throw new JsonVerifyException(file.getName(), exception); } }
  28. Taking Input consumer/json/sample.json { "id": 1, "name": "A green door",

    "price": 12.50, "tags": ["home", "green"], } $ ./gradlew :consumer:preBuild [composite-build] Configuring build: /Users/bsills/android/backup/gradle-json-verify :gradle-json-verify:compileJava :gradle-json-verify:pluginDescriptors UP-TO-DATE :gradle-json-verify:processResources UP-TO-DATE :gradle-json-verify:classes :gradle-json-verify:jar :consumer:jsonVerify FAILED FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':consumer:jsonVerify'. > Expected name at path $.tags in file: sample.json * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. BUILD FAILED Total time: 1.129 secs
  29. Taking Input JsonVerifyTask.java @TaskAction public void jsonVerify() { ConfigurableFileTree configFiles

    = getProject().fileTree(getProject().getProjectDir() + "/json/"); configFiles.forEach(file -> { if (".json".equals(getFileExtension(file))) { verifyJsonFile(file); } }); } {... continued ...}
  30. Plugin Extensions consumer/build.gradle {...} apply plugin: 'ninja.bryansills.gradle-json-verify' jsonVerify { srcDir

    = "whatever/you/want/" } package ninja.bryansills; public class JsonVerifyExtension { String srcDir = "/json/"; public String getSrcDir() { return srcDir; } public void setSrcDir(String srcDir) { this.srcDir = srcDir; } }
  31. Plugin Extensions public class JsonVerifyPlugin implements Plugin<Project> { @Override public

    void apply(Project project) { project.getExtensions().create("jsonVerify", JsonVerifyExtension.class); Task jsonVerifyTask = project.getTasks().create("jsonVerify", JsonVerifyTask.class); {... continued ...} public class JsonVerifyTask extends DefaultTask { @TaskAction public void jsonVerify() { JsonVerifyExtension extension = (JsonVerifyExtension) getProject().getExtensions().findByName("jsonVerify"); ConfigurableFileTree configFiles = getProject().fileTree(getProject().getProjectDir() + "/" + extension.getSrcDir()); {... continued ...}