Ввод паролей при сборке проектов с помощью gradle

Original author: Tim Roes
  • Translation
При сборке проектов для Android Gradle позволяет указать некоторые параметры, позволяющие собрать и подписать пакет, готовый для загрузки в Google Play. Однако, вряд ли стоит загружать некоторые данные, такие как пароль от приватного ключа в публичный репозиторий. В статье, перевод которой ниже, автор рассматривает способы ввода приватной информации, такой как пароли, во время сборки проекта.

Gradle позволяет получить доступ к консоли с помощью метода System.console(). Консоль предоставляет метод для чтения паролей, поэтому для ввода пароля можно использовать:
def password = System.console().readPassword("\nPlease enter key passphrase: ")

Теперь можно использовать пароль в любом месте скрипта сборки, и все готово… ой, тогда это будет слишком короткий пост, поэтому теперь поговорим о проблемах.


Проблема №1 — не нужно донимать меня постоянно.


Если вы просто разместите эту строку в ваш build.gradle, вы заметите, что она выполняется каждый раз, когда вы что-то собираете. Не важно, нужно ли вам подписать что-нибудь в этой сборке или нет, пароль будет запрошен в любом случае.

Для решения этой проблемы, можно использовать TaskGraph, чтобы проверить, исполняется ли задача, которая нуждается в этом ключе. Поскольку taskGraph будет заполнен в начале процесса сборки, нужно подождать, пока он не будет заполнен:
gradle.taskGraph.whenReady { taskGraph ->
  // Выполняется, когда TaskGraph готов.
}

Просто разместите этот кусок кода в скрипт сборки и код внутри него будет выполнен, когда граф будет готов.

Теперь необходимо проверить, что мы выполняем задачу, которая нуждается в вводе пароля. В TaskGraph имеется метод hasTask(), предназначенный лдя того, что определенная задача будет исполняться во время сборки. Необходимо указать имя задачи в качестве параметра. Также, вы должны указать двоеточие перед именем корневого каталога. Если задача определена в каком-то подмодуле (как это обычно бывает в android-проектах), вы также должны указать имя этого модуля. Пусть в вашем проекте есть модуль app и нам нужен пароль от ключа, когда мы выполняем assembleRelease. Так мы можем сделать необходимую проверку:
gradle.taskGraph.whenReady { taskGraph ->
  if(taskGraph.hasTask(':app:assembleRelease')) {
    //Выполняется только тогда, когда мы делаем релизную сборку 
    def pass = System.console().readPassword("\nPlease enter key passphrase: ")
    // readPassword возвращает  char[], поэтому нам нужно обернуть результат в String 
    pass = new String(pass)
    // А здесь можно использовать переменную pass 
  }
}

Теперь gradle не будет вас беспокоить, пока ему не понадобится пароль.

Проблема №2 — У нас нет консоли.


Если вы попробуете выполнить код, приведённый выше в IDE (например, в Android Studio) или с помощью gradle.daemon, у вас не будет доступа к консоли (System.console() вернёт null) и сборка сломается из-за исключения. Но не стоит паниковать, эта проблема решаема. Если у нас нет доступа к консоли, у нас все еще есть UI. Мы можем использовать SwngBuilder из Groovy, чтобы показать простой диалог ввода пароля.

Во-первых, необходимо его импортировать, так что разместите следующую строку в начале вашего build.gradle:
import groovy.swing.SwingBuilder

Теперь вы можете использовать SwingBuilder, чтобы показать простой диалог ввода:
def pass = ''
new SwingBuilder().edt {
  dialog(modal: true, //иначе сборка продолжится до того, как вы закроете диалог.
      title: 'Enter password',
      alwaysOnTop: true,
      resizable: false,
      locationRelativeTo: null, // Расположить диалог в центре экрана.
      pack: true,
      show: true
  ) {
    vbox {
      label(text: "Please enter key passphrase:")
      input = passwordField()
      button(defaultButton: true, text: 'OK', actionPerformed: {
        pass = input.password;
        dispose(); 
      })
    }
  }
}

Добавьте этот код туда, где вам нужно запросить пароль и вы получите введённый пользователем пароль в переменной pass.

Соединяем всё вместе.


Давайте теперь соберём все это вместе. UI хорош (ок, тот, который мы использовали ранее не очень хорош, но вы свободны модифицировать его как хотите: SwingBuilder docs), но возможно иногда вам нужно будет собирать на системе, где есть только консоль и нет графического интерфейса (как сервер сборки) и иногда из вашей IDE. Также возможно, вы хотели бы отменить сборку, если пользователь не ввёл пароль. Теперь ваш скрипт сборки должен выглядеть так:
gradle.taskGraph.whenReady { taskGraph ->
  if(taskGraph.hasTask(':app:assembleRelease')) {
    def pass = ''
    if(System.console() == null) {
      new SwingBuilder().edt {
        dialog(modal: true,
            title: 'Enter password',
            alwaysOnTop: true,
            resizable: false,
            locationRelativeTo: null,
            pack: true,
            show: true
        ) {
          vbox { 
            label(text: "Please enter key passphrase:")
            input = passwordField()
            button(defaultButton: true, text: 'OK', actionPerformed: {
              pass = input.password
              dispose();
            })
          }
        }
      }
    } 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.")
    }
 
    // -----
    // Здесь можно делать с переменной pass все, что нужно!
    // -----
 
  }
} 

Не стесняйтесь погрузиться в документацию groovy и посмотреть, как вы могли бы улучшить этот UI.
  • +18
  • 4.9k
  • 2
Share post
AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 2

    +2
    На OS X можно хранить подобную информацию в Keychain и запрашивать ее оттуда при сборке
      +2
      Как по мне, то хранить пароли в файле удобнее. Примерно так: stackoverflow.com/a/19603006/3047139
      Естественно этот файл не коммитим в репозиторий.

      У меня вот так:

      build.gradle:
      def Properties props = new Properties()
      def propFile = new File('signing.properties')
      if (propFile.canRead()){
          props.load(new FileInputStream(propFile))
      
          if (props!=null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
                  props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
              android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
              android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
              android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
              android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
          } else {
              println 'signing.properties found but some entries are missing'
              android.buildTypes.release.signingConfig = null
          }
      } else {
          println 'signing.properties not found'
          android.buildTypes.release.signingConfig = null
      }
      


      signing.properties
      STORE_FILE=...
      STORE_PASSWORD=...
      KEY_ALIAS=...
      KEY_PASSWORD=...
      

      Only users with full accounts can post comments. Log in, please.