Using password prompts with gradle build files

In my post Handling signing configs with gradle I introduced several options how to extract private signing information from your build file and store it outside of your repository.

One questions remained in that post: How to handle passwords of signing keys in your build file. In the previous article I just put them to the external build or property files. Depending on where you store these you might not want to put plaintext passwords in there – perhaps you even don’t want to do this, if you store your property files secure.

This article describes how to ask the user for a password during the build process.

Gradle gives you access to the console via System.console(). The console offers a method to read passwords, so that you can do:

def password = System.console().readPassword("\nPlease enter key passphrase: ")

Now use the content of password wherever you need to apply the password, and we are done.. oh no wait, would be a very short post, so let’s talk about the problems.

Problem #1 – Don’t bother me all the time

If you put this line somewhere in your build.gradle you will notice that it will execute this line every time you build something. It doesn’t care if you need to sign anything in this build or not, it will ask you anyway.

To solve that problem we can use the TaskGraph in Gradle to check if we are executing a task, that needs the key at all. Since the task graph will be populated in the beginning of the build (but won’t be finished until the build files have been read) we need to wait until the taskGraph has been populated:

gradle.taskGraph.whenReady { taskGraph ->
  // Executed when the task graph is ready (and can be accessed via taskGraph parameter)
}

Just place this snippet in your build file and the inner code will execute when the task graph is ready.

So now let’s check if we are executing a task that needs the password. The task graph has a method hasTask() to check whether a specific task will be executed during this build. You need to specify the task name as a parameter. You must also add a colon in front of the name for the root directory. If the task is defined in a specific submodule (as it usually is in Android projects) you also have to specify this modules name. Let’s assume your project has a module named app (as Android Studio normally creates it) and we need the key password if we execute assembleRelease. We can now do the following check:

gradle.taskGraph.whenReady { taskGraph ->
  if(taskGraph.hasTask(':app:assembleRelease')) {
    // Only execute when we are trying to assemble a release build
    def pass = System.console().readPassword("\nPlease enter key passphrase: ")
    // readPassword returns a char[] so we need to wrap it into a string, because that's
    // most likely what you need
    pass = new String(pass)
    // Use the pass variable here (so set signing configs here)
  }
}

Now Gradle won’t bother you unless you need the password.

Problem #2 – We don’t have a console

If you try to execute the above from inside an IDE (like Android Studio) or with gradle.daemon turned on, you won’t have a console (System.console() will return null) and your build will fail due to an exception. But no panic, let’s solve that problem. If we don’t have access to a console, we can still use a UI. We can use Groovy’s SwingBuilder to show a simple password input dialog.

At first you need to import at, so put the following line at top of your build.gradle file:

import groovy.swing.SwingBuilder

Now you can use the SwingBuilder to show a simple input dialog:

def pass = ''
new SwingBuilder().edt {
  dialog(modal: true, // Otherwise the build will continue running before you closed the dialog
      title: 'Enter password', // Dialog title
      alwaysOnTop: true, // pretty much what the name says
      resizable: false, // Don't allow the user to resize the dialog
      locationRelativeTo: null, // Place dialog in center of the screen
      pack: true, // We need to pack the dialog (so it will take the size of it's children
      show: true // Let's show it
  ) {
    vbox { // Put everything below each other
      label(text: "Please enter key passphrase:")
      input = passwordField()
      button(defaultButton: true, text: 'OK', actionPerformed: {
        pass = input.password; // Set pass variable to value of input field
        dispose(); // Close dialog
      })
    }
  }
}

Insert this code at the place where you need to ask for the password and you will get the user entered password in the pass variable.

Put it all together

Let’s put it all together. A UI is nice (okay the above one isn’t, but feel free to modify the dialog in whatever way you want: SwingBuilder docs) but perhaps sometimes you will build from a system that has only a console not a graphical interface (like a build server) and sometimes from your IDE. Also you would like to cancel the build if the user didn’t enter a password. So your final build file can look like that:

gradle.taskGraph.whenReady { taskGraph ->
  if(taskGraph.hasTask(':app:assembleRelease')) {

    def pass = ''
    if(System.console() == null) {
      new SwingBuilder().edt {
        dialog(modal: true, // Otherwise the build will continue running before you closed the dialog
            title: 'Enter password', // Dialog title
            alwaysOnTop: true, // pretty much what the name says
            resizable: false, // Don't allow the user to resize the dialog
            locationRelativeTo: null, // Place dialog in center of the screen
            pack: true, // We need to pack the dialog (so it will take the size of it's children)
            show: true // Let's show it
        ) {
          vbox { // Put everything below each other
            label(text: "Please enter key passphrase:")
            input = passwordField()
            button(defaultButton: true, text: 'OK', actionPerformed: {
              pass = input.password; // Set pass variable to value of input field
              dispose(); // Close dialog
            })
          } // vbox end
        } // dialog end
      } // edt end
    } else {
      pass = System.console().readPassword("\nPlease enter key passphrase: ")
      pass = new String(pass)
    }

    if(pass.size() <= 0) {
      throw new InvalidUserDataException("You must enter a password to proceed.")
    }

    // -----
    // Do what you need to do with pass here!
    // -----

  } // end if has task
} // end whenReady

Feel free to dig into the Groovy documentation and see how you can improve this UI if you feel like. I know that you can also bind variables in Swing to model values, just seemed to much overhead for this tutorial.