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

Как минимизировать рутину, чтобы не спиться

Как минимизировать рутину, чтобы не спиться

Сергей Боиштян, Tinkoff – MOSDROID #8 Oxygen

Эта история для тех, кто чует, что рано или поздно он будет лидом, либо менеджером проекта, либо он просто никак не может написать свой первый gradle plugin.

Я расскажу как я столкнулся с передвижением задач на доске, проставлением “rc” в задачах руками, сборкой для тестировщиков и публикацией в play market, приуныл и выходил из этого состояния с помощью gradle, git и щепотки teamcity

MOSDROID

June 01, 2018
Tweet

More Decks by MOSDROID

Other Decks in Programming

Transcript

  1. Сделать сборку тестировщику 1. Саму сборку сделать просто a. Сделать

    apk b. Залить в hockeyapp, Beta 5 2. Не просто понять, что тестировать на сборке
  2. Почему не просто понять • Задача и сборка не связаны

    • Может быть несколько сборок параллельно 6
  3. Решение • Собрать apk ◦ versionName, versionCode, archiveName связать с

    версией • Отправить в hockeyApp, beta • Перевести задачи в нужный статус • Проставить внутри задач версию для связи со сборкой 7
  4. Решение, взятое за основу • Есть ветка в git ◦

    В нее попадают изменения ◦ Триггерится сборка на CI ◦ Она собирает apk ▪ versionName берется из локального файла ▪ versionCode - teamcity buildNumber ◦ Apk попадает в hockeyApp ◦ Задачи меняют статус ◦ versionName, versionCode проставляются в задаче 11
  5. Недостатки решения • Нет явного триггера • Для инкремента версии,

    нужно сделать commit • versionCode не явно связан с историей git 12
  6. Инкремент версии: хранение git tag -a 1.0.0-rc1 -m ‘message’ •

    Не ломает историю • Легче добавить/удалить на remote • Своя система прав 15
  7. Логика инкремента версии После триггера • я получаю список задач,

    готовых для тестирования • беру из них версию • нахожу в git максимальный tag • сравниваю и инкрементирую, если нужно 16
  8. Не все так гладко. “apply from:” вынес в myTask.gradle import

    our.dependency.Task task myTask { Task().execute() } 29
  9. Не все так гладко. “apply from:” В build.gradle apply from:

    'myTask.gradle' FAILURE: Build failed with an exception. unable to resolve class our.dependency.Task 30
  10. 32 //build.gradle buildscript { repositories { ... } dependencies {

    classpath "org._10ne.gradle:rest-gradle-plugin:0.4.2" //any external plugin can be used } } apply plugin: "org.tenne.rest" task fromMain(type: org._10ne.gradle.rest.RestTask) { //works fine //... } apply from: 'gradle/other.gradle'
  11. 33 //other.gradle task fromOther(type: org._10ne.gradle.rest.RestTask) { //fails //... } FAILURE:

    Build failed with an exception. * Where: Script '/tmp/plugin-class-other-script/gradle/other.gradle' line: 1 * What went wrong: A problem occurred evaluating script. > Could not get unknown property 'org' for root project 'plugin-class-other-script' of type org.gradle.api.Project.
  12. Итоги Build script Вам этого достаточно, если: Вы не хотите

    шарить между (android) проектами Вы не хотите писать на других JVM языках Вы не адекватный Вы в самом начале 34
  13. Plugin build.gradle apply plugin: 'groovy' apply plugin: 'idea' repositories {

    mavenCentral() } dependencies { compile localGroovy() compile gradleApi() compile 'commons-io:commons-io:2.4' testCompile 'junit:junit:4.12' } 38
  14. Plugin class class HockeyAppPlugin implements Plugin<Project> { void apply(Project project)

    { project.extensions.create('hockeyapp', HockeyAppPluginExtension, project) if (project.plugins.hasPlugin(AppPlugin)) { AppExtension android = project.android android.applicationVariants.all { variant -> def taskName = "upload${variant.name.capitalize()}ToHockeyApp" def uploadTask = project.tasks.create(taskName, HockeyAppUploadTask) uploadTask.group = 'HockeyApp' uploadTask.dependsOn variant.assemble } } } 40
  15. Plugin class - create task class HockeyAppPlugin implements Plugin<Project> {

    void apply(Project project) { project.extensions.create('hockeyapp', HockeyAppPluginExtension, project) if (project.plugins.hasPlugin(AppPlugin)) { AppExtension android = project.android android.applicationVariants.all { variant -> def taskName = "upload${variant.name.capitalize()}ToHockeyApp" def uploadTask = project.tasks.create(taskName, HockeyAppUploadTask) uploadTask.group = 'HockeyApp' uploadTask.dependsOn variant.assemble } } } 41
  16. Plugin class - extention class HockeyAppPlugin implements Plugin<Project> { void

    apply(Project project) { project.extensions.create('hockeyapp', HockeyAppPluginExtension) //... } } } 42
  17. Plugin class - связь с build chain class HockeyAppPlugin implements

    Plugin<Project> { void apply(Project project) { project.extensions.create('hockeyapp', HockeyAppPluginExtension, project) if (project.plugins.hasPlugin(AppPlugin)) { AppExtension android = project.android android.applicationVariants.all { variant -> def taskName = "upload${variant.name.capitalize()}ToHockeyApp" def uploadTask = project.tasks.create(taskName, HockeyAppUploadTask) uploadTask.group = 'HockeyApp' uploadTask.dependsOn variant.assemble } } } 43
  18. Task - group name class HockeyAppPlugin implements Plugin<Project> { void

    apply(Project project) { project.extensions.create('hockeyapp', HockeyAppPluginExtension, project) if (project.plugins.hasPlugin(AppPlugin)) { AppExtension android = project.android android.applicationVariants.all { variant -> def taskName = "upload${variant.name.capitalize()}ToHockeyApp" def uploadTask = project.tasks.create(taskName, HockeyAppUploadTask) uploadTask.group = 'HockeyApp' } } } } 45
  19. Extension for users hockeyapp { notify = 1 status =

    2 notesType = 0 releaseType = 0 teamCityLog = true repositoryUrl = '' variantToApplicationId = [] } 47
  20. Extention class class HockeyAppPluginExtension { def Object outputDirectory def File

    symbolsDirectory = null def String apiToken = null def Map<String, String> variantToApiToken = null def Map<String, String> variantToNotes = null def String status = 2 def String strategy = "add" def String notify = 0 def Map<String, String> variantToNotify = null //... } 48
  21. Зависимость между task Есть два плагина: class Hello implements Plugin<Project>{

    @Override void apply(Project project) { project.tasks.create('hello') { doLast { println "Hello, it's me" } } } } class IWasWondering implements Plugin<Project> { @Override void apply(Project project) { project.tasks.create('iWasWondering') { doLast { println "I was wondering if after all these years\n" + " You 'd like to meet" } } } } 52
  22. Зависимость между task - DependsOn class Hello implements Plugin<Project>{ @Override

    void apply(Project project) { project.tasks.create('hello') { doLast { println "Hello, it's me" } } } } class IWasWondering implements Plugin<Project> { @Override void apply(Project project) { project.tasks.create('iWasWondering') { dependsOn(project.hello) doLast { println "I was wondering if after all these years\n" + " You 'd like to meet" } } } } 55
  23. DependsOn результат №1 ./gradlew iWasWondering Заработает? A problem occurred evaluating

    project ':ui'. > Failed to apply plugin [class 'IWasWondering'] > Could not get unknown property 'hello' for task ':ui:iWasWondering' of type org.gradle.api.DefaultTask. 56
  24. DependsOn поиск проблемы Может дело в порядке apply: Было in

    build.gradle apply plugin: IWasWondering apply plugin: Hello Стало in build.gradle apply plugin: Hello apply plugin: IWasWondering 57
  25. Executing tasks: [:ui:iWasWondering] :ui:hello Hello it's me :ui:iWasWondering I was

    wondering if after all these years You'd like to meet BUILD SUCCESSFUL in 0s 58
  26. Execute build.gradle apply plugin: Hello class Hello implements Plugin<Project>{ @Override

    void apply(Project project) { project.tasks.create('hello') { doLast { println "Hello, it's me" } } } } 61
  27. Случайная последовательность apply class IWasWondering implements Plugin<Project> { @Override void

    apply(Project project) { project.tasks.create('iWasWondering') { doLast { println "I was wondering if after all these years\n" + " You 'd like to meet" } } project.with { afterEvaluate { iWasWondering.dependsOn(hello) } } } } 62
  28. FinalizedBy class Hello implements Plugin<Project> { @Override void apply(Project project)

    { project.tasks.create('hello') { finalizedBy('iWasWondering') doLast { println "Hello, it's me" } } } } 63
  29. Executing tasks: [:ui:hello] :ui:hello Hello it's me :ui:iWasWondering I was

    wondering if after all these years You'd like to meet BUILD SUCCESSFUL in 0s 64
  30. MustRunAfter “When you use the “must run after” ordering rule

    you specify that taskB must always run after taskA, whenever both taskA and taskB will be run. This is expressed as taskB.mustRunAfter(taskA). “ 65
  31. MustRunAfter class IWasWondering implements Plugin<Project> { @Override void apply(Project project)

    { project.tasks.create('iWasWondering') { doLast { println "I was wondering if after all these years\n" + " You 'd like to meet" } mustRunAfter(project.hello) } } } 66
  32. ./gradlew hello A) Hello it’s me C) Hello it’s me

    I was wondering if after all these years You’d like to meet B) I was wondering if after all these years You’d like to meet Hello it’s me D) Невозможно спрогнозировать 67
  33. ./gradlew hello A) Hello it’s me C) Hello it’s me

    I was wondering if after all these years You’d like to meet B) I was wondering if after all these years You’d like to meet Hello it’s me D) Невозможно спрогнозировать 68
  34. Executing tasks: [hello, iWasWondering] :ui:hello Hello, it's me :ui:iWasWondering I

    was wondering if after all these years You 'd like to meet BUILD SUCCESSFUL in 0s 69
  35. Вернемся к dependsOn class Hello implements Plugin<Project>{ @Override void apply(Project

    project) { project.tasks.create('hello') { doLast { println "Hello, it's me" } } } } class IWasWondering implements Plugin<Project> { @Override void apply(Project project) { project.tasks.create('iWasWondering') { dependsOn(project.hello) doLast { println "I was wondering if after all these years\n" + " You 'd like to meet" } } } } 70
  36. ./gradlew iWasWondering hello A) Hello it’s me Hello it’s me

    I was wondering if after all these years You’d like to meet C) Hello it’s me I was wondering if after all these years You’d like to meet Hello it’s me B) Hello it’s me I was wondering if after all these years You’d like to meet D) Невозможно спрогнозировать 71
  37. ./gradlew iWasWondering hello A) Hello it’s me Hello it’s me

    I was wondering if after all these years You’d like to meet C) Hello it’s me I was wondering if after all these years You’d like to meet Hello it’s me B) Hello it’s me I was wondering if after all these years You’d like to meet D) Невозможно спрогнозировать 72
  38. В какой фазе выполняется? android { defaultConfig { versionCode androidVersionCode()

    versionName androidVersionName() } } def androidVersionName() {} def androidVersionCode() {} 73
  39. Тестирование class AndroidGitVersionTest extends GroovyTestCase { @Lazy TemporaryFolder projectFolder =

    { TemporaryFolder folder = new TemporaryFolder() folder.create() folder.newFile("build.gradle") return folder }() @Lazy Project project = { ProjectBuilder.builder() .withProjectDir(projectFolder.root) .build() }() 76
  40. Тестирование @Lazy TemporaryFolder projectFolder @Lazy Project project @Lazy AndroidGitVersionExtension plugin

    = { project.pluginManager.apply 'com.gladed.androidgitversion' def extension = project.getExtensions().getByName('androidGitVersion') assertTrue(extension instanceof AndroidGitVersionExtension) return (AndroidGitVersionExtension) extension }() void testNoGitRepo() { assertEquals('unknown', plugin.name()) assertEquals(0, plugin.code()) } 77