1+ @file:Suppress(" UnstableApiUsage" )
2+
3+ import java.io.FileInputStream
14import java.util.*
5+ import com.android.build.api.dsl.ApplicationExtension
6+ import com.android.build.api.dsl.LibraryExtension
7+ import com.android.build.gradle.BaseExtension
8+ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
9+ import org.kohsuke.github.GHReleaseBuilder
210import org.kohsuke.github.GitHub
311
4- plugins {
5- idea
6- }
7-
8- idea {
9- module {
10- isDownloadSources = true
11- isDownloadJavadoc = true
12+ buildscript {
13+ dependencies {
14+ classpath(" com.android.tools.build:gradle:8.2.0" )
15+ classpath(" org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22" )
16+ classpath(" org.kohsuke:github-api:1.316" )
17+ }
18+
19+ repositories {
20+ gradlePluginPortal()
21+ google()
22+ mavenCentral()
1223 }
1324}
1425
1526allprojects {
16- apply {
17- plugin(" idea" )
18- }
19-
20- idea {
21- module {
22- isDownloadSources = true
23- isDownloadJavadoc = true
24- }
25- }
26-
27- // apply common in afterEvaluate because other plugins need to be loaded first
27+ // apply in afterEvaluate because other plugins need to be loaded first
2828 afterEvaluate {
29- apply {
30- plugin(" common" )
29+
30+ val isAndroid = plugins.hasPlugin(" com.android.application" )
31+ val isAndroidLib = plugins.hasPlugin(" com.android.library" )
32+ // val isJavaLib = plugins.hasPlugin("java-library")
33+ val isKotlinLib = plugins.hasPlugin(" org.jetbrains.kotlin.jvm" )
34+ val isKotlinAndroid = plugins.hasPlugin(" org.jetbrains.kotlin.android" )
35+
36+ // if (isAndroid) println("android")
37+ // if (isAndroidLib) println("android lib")
38+ // if (isJavaLib) println("java lib")
39+ // if (isKotlinLib) println("kotlin lib")
40+ // if (isKotlinAndroid) println("kotlin android")
41+
42+ val javaVersion = JavaVersion .VERSION_17
43+
44+ val commitCount = getCommitCount()
45+ val commitHash = getCommitHash()
46+ val workingTreeClean = getWorkingTreeClean()
47+ val allCommitsPushed = getAllCommitsPushed()
48+
49+ if (isAndroid || isAndroidLib) {
50+ val android = extensions.getByType<BaseExtension >()
51+ with (android) {
52+ val propsFile = rootProject.file(" keystore.properties" )
53+ if (propsFile.exists()) {
54+ val props = Properties ()
55+ props.load(FileInputStream (propsFile))
56+
57+ signingConfigs {
58+ maybeCreate(" release" ).apply {
59+ storeFile = storeFile ? : rootProject.file(props[" storeFile" ].toString())
60+
61+ check(storeFile != null && storeFile!! .exists()) { " keystore does not exist" }
62+
63+ storePassword = storePassword ? : props[" storePassword" ].toString()
64+ keyAlias = keyAlias ? : props[" keyAlias" ].toString()
65+ keyPassword = keyPassword ? : props[" keyPassword" ].toString()
66+
67+ maybeCreate(" debug" ).let { debug ->
68+ debug.storeFile = storeFile
69+ debug.storePassword = storePassword
70+ debug.keyAlias = keyAlias
71+ debug.keyPassword = keyPassword
72+ }
73+ }
74+ }
75+ defaultConfig {
76+ signingConfig = signingConfigs.getByName(" release" )
77+ }
78+ } else {
79+ defaultConfig {
80+ signingConfig = signingConfigs.getByName(" debug" )
81+ }
82+ }
83+
84+ defaultConfig {
85+ versionCode = commitCount
86+ versionName = " $commitCount${if (workingTreeClean) " -" else " +" }$commitHash "
87+
88+ proguardFiles(getDefaultProguardFile(" proguard-android-optimize.txt" ), " ../proguard-rules.pro" )
89+ if (isAndroidLib) {
90+ consumerProguardFiles(" consumer-rules.pro" )
91+ }
92+ }
93+
94+ buildTypes {
95+ getByName(" release" ) {
96+ isMinifyEnabled = true
97+ multiDexEnabled = false
98+
99+ if (isAndroid) {
100+ isShrinkResources = true
101+ }
102+ }
103+ getByName(" debug" ) {
104+ isMinifyEnabled = false
105+ isShrinkResources = false
106+ multiDexEnabled = false
107+ versionNameSuffix = " -dev"
108+ }
109+ }
110+
111+ compileOptions {
112+ sourceCompatibility = javaVersion
113+ targetCompatibility = javaVersion
114+ }
115+ }
116+ }
117+
118+ if (isAndroid) {
119+ val common = extensions.getByType<ApplicationExtension >()
120+ with (common) {
121+ compileSdk = 34
122+
123+ dependenciesInfo {
124+ includeInApk = false
125+ includeInBundle = false
126+ }
127+
128+ buildFeatures {
129+ buildConfig = true
130+ }
131+
132+ lint {
133+ disable + = " DiscouragedApi"
134+ disable + = " ExpiredTargedSdkVersion"
135+ disable + = " OldTargetApi"
136+ disable + = " MissingApplicationIcon"
137+ disable + = " UnusedAttribute"
138+ }
139+ }
140+ }
141+
142+ if (isAndroidLib) {
143+ val common = extensions.getByType<LibraryExtension >()
144+ with (common) {
145+ compileSdk = 34
146+ }
147+ }
148+
149+ if (isAndroid) {
150+ val android = extensions.getByType<BaseExtension >()
151+
152+ tasks.register(" createGithubRelease" ) {
153+ check(workingTreeClean) { " Commit all changes before creating release" }
154+ check(allCommitsPushed) { " Push to remote before creating release" }
155+
156+ dependsOn(" assembleRelease" )
157+
158+ val properties = Properties ()
159+ val file = rootProject.file(" local.properties" )
160+ if (file.exists()) {
161+ properties.load(file.inputStream())
162+ }
163+
164+ val repo = properties[" github_repo" ]
165+ val token = properties[" github_api_key" ]
166+
167+ check(repo != null && repo is String ) { " github_repo not provided in local.properties" }
168+ check(token != null && token is String ) { " github_api_key not provided in local.properties" }
169+
170+ doFirst {
171+ val packageRelease = project.tasks.getByName<DefaultTask >(" packageRelease" )
172+
173+ val outputs = packageRelease.outputs.files
174+ val apks = outputs.filter { it.isDirectory }.flatMap { it.listFiles { file -> file.extension == " apk" }!! .toList() }
175+
176+ val github = GitHub .connectUsingOAuth(token)
177+ val repository = github.getRepository(repo)
178+
179+ val tagName = " ${android.namespace} -v$commitCount "
180+ val name = " ${project.name} -v$commitCount "
181+
182+ if (repository.getReleaseByTagName(tagName) != null ) {
183+ doLast {
184+ println (" Release $name already exists" )
185+ }
186+ return @doFirst
187+ }
188+
189+ val release = repository.createRelease(tagName).name(name).draft(true ).makeLatest(GHReleaseBuilder .MakeLatest .FALSE ).create()
190+
191+ apks.forEach {
192+ release.uploadAsset(" ${project.name} -v$commitCount .apk" , it.inputStream(), " application/vnd.android.package-archive" )
193+ }
194+
195+ doLast {
196+ println (" Created release ${release.name} : ${release.htmlUrl} " )
197+ }
198+ }
199+ }
200+
201+ android.packagingOptions {
202+ resources.excludes + = listOf (
203+ " **/*.kotlin_builtins" ,
204+ " **/*.kotlin_metadata" ,
205+ " **/*.kotlin_module" ,
206+ " kotlin-tooling-metadata.json" ,
207+ )
208+ }
209+ }
210+
211+ tasks.withType<KotlinCompile > {
212+ kotlinOptions.jvmTarget = javaVersion.toString()
213+ }
214+
215+ tasks.withType<JavaCompile > {
216+ sourceCompatibility = javaVersion.toString()
217+ targetCompatibility = javaVersion.toString()
218+ }
219+
220+ if (isAndroid || isAndroidLib) {
221+ dependencies {
222+ add(" compileOnly" , " de.robv.android.xposed:api:82" )
223+ }
224+ }
225+
226+ if (isKotlinLib || isKotlinAndroid) {
227+ dependencies {
228+ add(" implementation" , " org.jetbrains:annotations:24.1.0" )
229+ }
31230 }
32231 }
33232}
@@ -53,3 +252,50 @@ tasks.create("clearAllDraftReleases") {
53252 }
54253 }
55254}
255+
256+ // <editor-fold desc="Git">
257+ fun Project.getCommitCountExec () = providers.exec {
258+ executable(" git" )
259+ args(" rev-list" , " --count" , " HEAD" , projectDir.absolutePath)
260+
261+ extensions.findByType<BaseExtension >()?.let {
262+ val metadata = rootProject.file(" metadata" ).resolve(it.namespace!! )
263+ if (metadata.exists()) {
264+ args(metadata)
265+ }
266+ }
267+ }
268+
269+ fun Project.getCommitHashExec () = providers.exec {
270+ executable(" git" )
271+ args(" rev-parse" , " --short" , " HEAD" )
272+ }
273+
274+ fun Project.getWorkingTreeCleanExec () = providers.exec {
275+ executable(" git" )
276+ args(" diff" , " --quiet" , " --exit-code" , rootDir.absolutePath)
277+ isIgnoreExitValue = true
278+ }
279+
280+ fun Project.getAllCommitsPushedExec (): ExecOutput {
281+ providers.exec {
282+ executable(" git" )
283+ args(" fetch" )
284+ isIgnoreExitValue = true
285+ }
286+
287+ return providers.exec {
288+ executable(" git" )
289+ args(" diff" , " --quiet" , " --exit-code" , " origin/main..main" )
290+ isIgnoreExitValue = true
291+ }
292+ }
293+
294+ fun Project.getCommitCount () = getCommitCountExec().standardOutput.asText.get().trim().toInt()
295+
296+ fun Project.getCommitHash () = getCommitHashExec().standardOutput.asText.get().trim()
297+
298+ fun Project.getWorkingTreeClean () = getWorkingTreeCleanExec().result.orNull?.exitValue == 0
299+
300+ fun Project.getAllCommitsPushed () = getAllCommitsPushedExec().result.orNull?.exitValue == 0
301+ // </editor-fold>
0 commit comments