Skip to content

Commit 7b97ff9

Browse files
committed
Added YearMonth extensions
1 parent 958a25c commit 7b97ff9

File tree

3 files changed

+256
-25
lines changed

3 files changed

+256
-25
lines changed

Libraries under AGP 9.0.0.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# Publishing Libraries under AGP 9.0.0
2+
3+
## Background
4+
5+
AGP 9.0.0 removes or breaks several of the APIs your snippet depends on:
6+
- ```android.sourceSets``` → replaced by the new **Android Components DSL**
7+
- ```android.bootClasspath``` → removed; replaced by ```androidComponents.sdkComponents.bootClasspath```
8+
- ```Javadoc``` **tasks no longer work for Android sources**, because Android uses **Kotlin + Java + generated sources**, and AGP no longer wires them into the old Java toolchain.
9+
10+
So the old pattern:
11+
12+
```kotlin
13+
tasks.withType<Javadoc> {
14+
source(android.sourceSets["main"].java.srcDirs)
15+
classpath += project.files(android.bootClasspath.joinToString(File.pathSeparator))
16+
exclude("**/R.class", "**/BuildConfig.class")
17+
}
18+
```
19+
…no longer works under AGP 9.
20+
21+
---
22+
23+
# ✅ The correct AGP 9 replacement
24+
25+
AGP 9 requires you to use the **Android Components DSL** and the new **SDK Components API**.
26+
27+
Here’s the modern equivalent:
28+
29+
```kotlin
30+
import com.android.build.api.variant.AndroidComponentsExtension
31+
import com.android.build.api.variant.SourceDirectories
32+
33+
plugins {
34+
id("com.android.library")
35+
kotlin("android")
36+
}
37+
38+
androidComponents {
39+
onVariants { variant ->
40+
tasks.register("javadoc${variant.name.capitalize()}", Javadoc::class.java) {
41+
// Source: use the new variant API
42+
val javaSources = variant.sources.java?.all
43+
if (javaSources != null) {
44+
source(javaSources.map { it.asFileTree })
45+
}
46+
47+
// Boot classpath: use sdkComponents
48+
val bootClasspath = sdkComponents.bootClasspath
49+
classpath = files(bootClasspath) + files(variant.compileClasspath)
50+
51+
// Exclusions
52+
exclude("**/R.class", "**/BuildConfig.class")
53+
}
54+
}
55+
}
56+
```
57+
---
58+
59+
## ⚠️ Important: Javadoc on Android is fragile
60+
61+
AGP 9 no longer guarantees:
62+
- Java-only sources
63+
- A stable bootClasspath
64+
- That Javadoc can run on mixed Kotlin/Java modules
65+
66+
Most Android libraries now use **Dokka** instead of Javadoc.
67+
68+
If your goal is to publish documentation, the recommended modern setup is:
69+
70+
```kotlin
71+
plugins {
72+
id("org.jetbrains.dokka") version "1.9.20"
73+
}
74+
```
75+
And then:
76+
77+
```text
78+
./gradlew dokkaHtml
79+
```
80+
81+
# 🎯 If your goal is Publishing a library to JitPack
82+
83+
Publishing an Android/Kotlin library to **JitPack** works beautifully until AGP 8/9 enters the picture.
84+
85+
Previous versions failed because **AGP 9** removes the old **Java source + bootClasspath APIs**, and JitPack relies on your project building cleanly *before* it can publish.
86+
87+
---
88+
89+
# ⭐ A Clean, JitPack‑friendly setup without fighting AGP - the key insight
90+
91+
**You do *NOT* need Javadoc tasks at all for JitPack**.
92+
93+
JitPack only needs:
94+
95+
1. Your project to build successfully
96+
2. A valid Maven publication (```maven-publish```)
97+
3. A Git tag or release
98+
99+
Javadoc generation is optional, and for Android libraries it’s now discouraged because AGP 9 breaks the old Javadoc pipeline.
100+
101+
So the simplest and most reliable path is:
102+
103+
### 👉 Remove the ```tasks.withType<Javadoc>``` block entirely
104+
105+
It is no longer compatible with AGP 9.
106+
107+
---
108+
109+
## ✅ The correct AGP‑9‑compatible setup for JitPack
110+
111+
### 1. Add the ```maven-publish``` plugin
112+
113+
In your library module:
114+
115+
```kotlin
116+
plugins {
117+
id("com.android.library")
118+
kotlin("android")
119+
`maven-publish`
120+
}
121+
```
122+
123+
---
124+
125+
### 2. Add a publication block
126+
127+
**AGP 8+** requires you to configure publishing after evaluation
128+
129+
```kotlin
130+
afterEvaluate {
131+
publishing {
132+
publications {
133+
create<MavenPublication>("release") {
134+
from(components["release"])
135+
136+
groupId = "com.github.YourUser"
137+
artifactId = "YourLibraryName"
138+
version = "1.0.0"
139+
}
140+
}
141+
}
142+
}
143+
```
144+
145+
This is the exact pattern **JitPack** expects for Android libraries.
146+
147+
---
148+
149+
### 3. Push to **GitHub** and create a tag
150+
151+
Example:
152+
153+
```html
154+
v1.0.0
155+
```
156+
157+
---
158+
159+
### 4. Go to *JitPack*
160+
161+
Visit:
162+
163+
```html
164+
https://jitpack.io/#YourUser/YourRepo
165+
```
166+
167+
JitPack will detect your tag and build your library.
168+
169+
---
170+
171+
### 5. Consumers add your library like this
172+
173+
**settings.gradle**
174+
175+
```kotlin
176+
dependencyResolutionManagement {
177+
repositories {
178+
mavenCentral()
179+
maven { url = uri("https://jitpack.io") }
180+
}
181+
}
182+
```
183+
184+
**build.gradle**
185+
186+
```kotlin
187+
dependencies {
188+
implementation("com.github.YourUser:YourRepo:1.0.0")
189+
}
190+
```
191+
192+
---
193+
194+
## 🎯 Summary
195+
196+
To publish your library on JitPack with AGP 9:
197+
198+
- **Delete the Javadoc task** (AGP 9 breaks it)
199+
- **Use ```maven-publish``` with ```afterEvaluate```**
200+
- **Tag** a **release**
201+
- Let **JitPack** build it

extensions/build.gradle.kts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,6 @@ android {
2323
}
2424
}
2525

26-
// Add this to your android block to generate Javadoc from Kotlin sources
27-
// (if you are using Dokka, the setup is different)
28-
// For basic Javadoc with KDoc:
29-
tasks.withType<Javadoc> {
30-
source(android.sourceSets["main"].java.srcDirs)
31-
classpath += files(android.ndkPath)
32-
// Exclude generated files if necessary
33-
exclude("**/R.class", "**/BuildConfig.class")
34-
}
35-
3626
publishing {
3727
singleVariant("release") {
3828
// This will automatically create a publication component for the "release" variant
@@ -50,20 +40,6 @@ dependencies {
5040
}
5141

5242

53-
// Add these tasks to create the sources and Javadoc JARs
54-
// Put these at the root level of your build.gradle.kts, outside publishing block
55-
//tasks.register<Jar>("sourcesJar") {
56-
// archiveClassifier.set("sources")
57-
//
58-
// from(android.sourceSets["main"].java.srcDirs) // Problematic for configuration cache
59-
// from(android.sourceSets["main"].kotlin.srcDirs()) // Problematic for configuration cache
60-
//}
61-
62-
//tasks.register<Jar>("javadocJar") {
63-
// archiveClassifier.set("javadoc")
64-
// from(tasks.named("javadoc")) // Depends on the standard javadoc task
65-
//}
66-
6743
afterEvaluate {
6844
publishing {
6945
publications {
@@ -85,7 +61,7 @@ afterEvaluate {
8561

8662
groupId = "com.github.forteanjo"
8763
artifactId = "extensions"
88-
version = "1.0.1"
64+
version = "1.0.2"
8965

9066
// Add these for sources and Javadoc
9167
// artifact(tasks.named("sourcesJar")) // Assumes you have a sourcesJar task (see below)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
@file:OptIn(ExperimentalTime::class)
2+
3+
package sco.carlukesoftware.extensions
4+
5+
import kotlinx.datetime.DateTimeUnit
6+
import kotlinx.datetime.LocalDate
7+
import kotlinx.datetime.TimeZone
8+
import kotlinx.datetime.YearMonth
9+
import kotlinx.datetime.minus
10+
import kotlinx.datetime.plus
11+
import kotlinx.datetime.toLocalDateTime
12+
import kotlin.time.Clock
13+
import kotlin.time.ExperimentalTime
14+
15+
fun YearMonth.Companion.now(
16+
zone: TimeZone = TimeZone.currentSystemDefault()
17+
): YearMonth {
18+
val dt = Clock.System.now().toLocalDateTime(zone)
19+
return YearMonth(dt.year, dt.month)
20+
}
21+
22+
/**
23+
* Returns the following month.
24+
*/
25+
val YearMonth.nextMonth: YearMonth
26+
get() = this.plus(1, DateTimeUnit.MONTH)
27+
28+
29+
/**
30+
* Returns the following month.
31+
*/
32+
val YearMonth.prevMonth: YearMonth
33+
get() = this.minus(1, DateTimeUnit.MONTH)
34+
35+
36+
/**
37+
* Combines this year-month with a day-of-month to create a `LocalDate`.
38+
* This returns a `LocalDate` with the year and month from this object,
39+
* and the provided day-of-month.
40+
*
41+
* @param day The day-of-month to use, from 1 to 31.
42+
* @return The resulting `LocalDate`.
43+
* @throws IllegalArgumentException if the day-of-month is invalid for the year and month.
44+
*/
45+
fun YearMonth.atDay(day: Int): LocalDate = LocalDate(this.year, this.month, day)
46+
47+
/**
48+
* Determines if a given year is a leap year.
49+
* A year is a leap year if it is divisible by 4, unless it is a century year not divisible by 400.
50+
*/
51+
fun YearMonth.isLeapYear(): Boolean {
52+
val year = this.year
53+
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
54+
}

0 commit comments

Comments
 (0)