developing android plugin android - - PowerPoint PPT Presentation

developing android plugin
SMART_READER_LITE
LIVE PREVIEW

developing android plugin android - - PowerPoint PPT Presentation

developing android plugin android team lead TFS Android


slide-1
SLIDE 1

developing android plugin

Сергей Боиштян

slide-2
SLIDE 2

обо мне

2

android разработчик архитектор team lead один из ведущих #AndroidDevPodcast преподаю в TFS Android один из организаторов «Mosdroid» член программного комитета «Mobius»

slide-3
SLIDE 3

о чем поговорим

3

о проблеме для которой мне понадобился plugin

  • том как писать

plugin о том как связать task из своего plugin со сторонними

slide-4
SLIDE 4

предисловие

4

я стал «Лидом»/ «Архитектором» появилось много ответственностей связанных с процессом например сделать apk для тестирования

slide-5
SLIDE 5

сделать сборку тестировщику

5

саму сборку сделать просто:

  • задача и сборка не связаны
  • может быть несколько сборок

параллельно

  • сделать apk
  • залить в hockeyapp, Beta

не просто понять, что тестировать на сборке

slide-6
SLIDE 6

алгоритм решения

6

сделать apk versionname, versioncode, archivename связать с версией

1

отправить в hockeyapp, beta

2

перевести задачи в нужный статус

3

проставить внутри задач версию для связи со сборкой

4

slide-7
SLIDE 7

алгоритм решения

7

hockeyAPP JIRA apk cвязать apk c версией статус задач версия в задачах

slide-8
SLIDE 8

8

проблемы

много шагов легко забыть что-нибудь нет явного триггера для действия можно заболеть

slide-9
SLIDE 9

9

slide-10
SLIDE 10

когда «накипело»

10

начал искать решения оказалось, что их много и было то, которое я взял за основу

slide-11
SLIDE 11

решение, взятое за основу

11

есть специальная ветка в git

в нее попадают изменения триггерится сборка на ci она собирает apk аpk попадает в hockeyApp задачи меняют статус versionname берется из локального файла versioncode — teamcity buildnumber versionName, versionCode проставляются в задаче

slide-12
SLIDE 12

алгоритм решения

12

поменять статус задачам

JIRA teamcity git hockeyAPP

проставить в задаче versioncode, versionname APK versionCode=buildNumber versionName=из файла инкремент versionCode commit

slide-13
SLIDE 13

недостатки решения

13

триггерим вручную для инкремента версии нужно сделать commit versionCode = синтетический buildNumber

slide-14
SLIDE 14

что я улучшил

14

изменил инкремент версии что позволило автоматизировать триггер, для формирования сборки

slide-15
SLIDE 15

инкремент версии: формат

15

формат — Semantic Versioning Specification MAJOR.MINOR.PATCH 1.0.0-rc1 1.1.1-rc2 1.2.3-beta

slide-16
SLIDE 16

инкремент версии: хранение

16

git tag -a 1.0.0-rc1 -m ‘message’

  • не ломает историю
  • легче добавить/удалить на remote
  • своя система прав
slide-17
SLIDE 17

триггер инкремента версии

17

когда появились задачи в нужном статусе в JIRA

slide-18
SLIDE 18

реализация инкремента версии

18

доработал https://github.com/gladed/gradle-android-git-version реализовал инкремент Из 1.1.0-rc10 в 1.1.1-rc1 Из 1.1.1-rc1 в 1.1.1-rc2 Из 1.1.10-rc2 в 1.2.1-rc1

slide-19
SLIDE 19

реализация инкремента версии

19

добавил:

  • запись в tag
  • push tag в remote
slide-20
SLIDE 20

поменять статус задачам

JIRA teamcity hockeyAPP

проставить в задаче versioncode, versionname apk versionCode=git tag versionName=git tag

мое решение

20

появление задач в Jira инкремент versionCode

slide-21
SLIDE 21

21

автоматизируем

➔ bash ➔ fastline (ruby) ➔ gradle ➔ etc.

slide-22
SLIDE 22

22

почему gradle?

нативная система сборки плагинная система возможность писать на java, kotlin, scala :? но кого я обманываю groovy

slide-23
SLIDE 23

знакомство

23

plugin writer plugin writer build script writer plugin writer build script writer app developer

slide-24
SLIDE 24

24

gadle plugin

легко шарить легче поддерживать легче тестировать

slide-25
SLIDE 25

структура плагина

25

➔ это gradle проект ➔ build.gradle ➔ plugin class ➔ descriptor ➔ one or more tasks ➔ extension

slide-26
SLIDE 26

структура плагина

26

plugin extention task descriptor build.gradle

slide-27
SLIDE 27

структура плагина

27

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' }

slide-28
SLIDE 28

28

plugin class

cоздание task cоздание extension cвязь с build chain

slide-29
SLIDE 29

plugin class

29

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 } } } }

slide-30
SLIDE 30

plugin class — create task

30

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 uploadT adTas ask = projec ect.tas asks.cr creat ate(tas askNam ame, Hockey eyAp AppUp UploadT adTas ask) uploadTask.group = 'HockeyApp' uploadTask.dependsOn variant.assemble } } } }

slide-31
SLIDE 31

plugin class — extention

31

class HockeyAppPlugin implements Plugin<Project> { void apply(Project project) { projec ect.ex exten ension ions.cre creat ate('hock

  • ckey

eyapp app', Hockey ckeyAppPlug uginE nExtens xtension

  • n)

//... } } }

slide-32
SLIDE 32

plugin class — связь с build chain

32

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' upload

adTask Task.de depe pends ndsOn On variant iant.asse semb mble le

} } } }

slide-33
SLIDE 33

task

33

class HockeyAppUploadTask extends DefaultTask { @TaskAction def upload() throws IOException { //... }

slide-34
SLIDE 34

task — group name

34

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) upload

adTask Task.gro group up = 'Hocke ckeyApp' p'

} } } }

slide-35
SLIDE 35

extension for users

35

hockeyapp { notify = 1 status = 2 notesType = 0 releaseType = 0 teamCityLog = true repositoryUrl = '' variantToApplicationId = [] }

slide-36
SLIDE 36

extention class

36

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 //... }

slide-37
SLIDE 37

find extention

37

HockeyAppPluginExtension hockeyApp = project.hockeyapp

slide-38
SLIDE 38

descriptor

38

implementation-class=de.felixschulze.gradle.HockeyAppPlugin apply plugin: ‘descriptor file name’

slide-39
SLIDE 39

подключение к своему проекту

39

In build.gradle apply plugin: ‘descriptor file name' hockeyapp { //... }

slide-40
SLIDE 40

зависимость между task

40

есть два плагина

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" } } } }

slide-41
SLIDE 41

зависимость между task

41

подключаем к проекту

in build.gradle apply plugin: IWasWondering apply plugin: Hello

slide-42
SLIDE 42

зависимость между task — DependsOn

42 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" } } } }

slide-43
SLIDE 43

dependsOn результат №1

43

./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.

slide-44
SLIDE 44

dependsOn поиск проблемы

44

может дело в порядке apply

было in build.gradle

apply plugin: IWasWondering apply plugin: Hello

стало in build.gradle

apply plugin: Hello apply plugin: IWasWondering

slide-45
SLIDE 45

45

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

slide-46
SLIDE 46

46

gradle build lifecycle

initialization configuration execution

slide-47
SLIDE 47

47

configuration

еxecute build.gradle beforeEvaluate afterEvaluate

slide-48
SLIDE 48

execute build.gradle

48

apply plugin: Hello class Hello implements Plugin<Project>{ @Override void apply(Project project) { project.tasks.create('hello') { doLast { println "Hello, it's me" } } } }

slide-49
SLIDE 49

cлучайная последовательность apply

49

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" } } pro roject ject.wit with { afterEv erEvalua luate { iWa WasWonderin ering.dep epen endsOn On(hel hello) } } } }

slide-50
SLIDE 50

finalizedBy

50

class Hello implements Plugin<Project> { @Override void apply(Project project) { project.tasks.create('hello') { fina

naliz lizedB dBy('iWa iWasWond Wonderi ering ng')

doLast {

println "Hello, it's me" } } } }

slide-51
SLIDE 51

51

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

slide-52
SLIDE 52

mustRunAfter

52

« »

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)

slide-53
SLIDE 53

mustRunAfter

53

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" } mustRunA

unAft fter(proj project ect.he hello llo)

} } }

slide-54
SLIDE 54

./gradlew hello

54

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 невозможно

спрогнозировать

slide-55
SLIDE 55

55

Executing tasks: [hell llo, iWasWonde

  • ndering

ing] :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

slide-56
SLIDE 56

вернемся к dependsOn

56 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" } } } }

slide-57
SLIDE 57

./gradlew iWasWondering hello

57

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 невозможно

спрогнозировать

slide-58
SLIDE 58

проверка внимательности

58

в какой фазе выполняется?

android { defaultConfig { versionCode androidVersionCode() versionName androidVersionName() } } def androidVersionName() {} def androidVersionCode() {}

slide-59
SLIDE 59

в какой фазе выполняется?

59

A initialization C execution B configuration D моя твоя не понимай

slide-60
SLIDE 60

60

дополнительные темы

UP-TO-DATE skip tasks debug scripts тестирование

slide-61
SLIDE 61

итоги

61

кучу готовых решений на рынке нужно разобраться в тонкостях автоматизируйте рутину

slide-62
SLIDE 62

ссылки

62

semantic versioning specification github.com/gladed/gradle-android-git-version github.com/x2on/gradle-hockeyapp-plugin