Skip to content

Commit ecbaa32

Browse files
committed
Yatagan 2.0 release notes and migration guide
1 parent 09ca4f6 commit ecbaa32

1 file changed

Lines changed: 332 additions & 0 deletions

File tree

MIGRATION.md

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
## Table of Contents
2+
3+
1. [New Features](#new-features)
4+
2. [Breaking Changes](#breaking-changes)
5+
3. [Deprecated APIs](#deprecated-apis)
6+
4. [Processor Options](#processor-options)
7+
8+
---
9+
10+
## New Features
11+
12+
### 1. Dagger Compatibility Mode
13+
14+
An experimental mode that allows Yatagan to recognize Dagger annotations, enabling gradual migration.
15+
16+
**Enable via processor option:**
17+
```kotlin
18+
ksp {
19+
arg("yatagan.experimental.enableDaggerCompatibility", "true")
20+
}
21+
```
22+
23+
**Features:**
24+
- Recognizes Dagger annotations (`@dagger.Component`, `@dagger.Module`, etc.)
25+
- Generates `Dagger<ComponentName>` bridge classes alongside Yatagan implementations
26+
- Yatagan and Dagger annotations can coexist; Yatagan takes priority when both present
27+
- Also supported in reflection backend via `enableDaggerCompatibility=true` property
28+
29+
---
30+
31+
### 2. Unified Entry Point
32+
33+
The `Yatagan` object in `api:public` now automatically handles both compiled and reflection backends:
34+
35+
```kotlin
36+
// Works with both backends
37+
val component = Yatagan.create(MyComponent::class.java)
38+
val builder = Yatagan.builder(MyComponent.Builder::class.java)
39+
```
40+
41+
The loader tries compiled implementation first, then falls back to reflection if available.
42+
43+
---
44+
45+
## Breaking Changes
46+
47+
### 1. `@Provides` Conditionals Removed
48+
49+
The `conditionals` parameter has been removed from `@Provides`. Use `@Conditional` as a separate annotation.
50+
51+
**Before (1.x.y):**
52+
```kotlin
53+
@Module
54+
object MyModule {
55+
@Provides(Conditional(FeatureA::class), Conditional(FeatureB::class))
56+
fun provideFoo(): Foo = FooImpl()
57+
}
58+
```
59+
60+
**After (2.0):**
61+
```kotlin
62+
@Module
63+
object MyModule {
64+
@Provides
65+
@Conditional(FeatureA::class)
66+
@Conditional(FeatureB::class)
67+
fun provideFoo(): Foo = FooImpl()
68+
}
69+
```
70+
71+
---
72+
73+
### 2. Generated Component Class Names
74+
75+
Generated implementation class names have changed to be more tooling-friendly.
76+
77+
**Before (1.x.y):**
78+
```
79+
Yatagan$MyComponent // Top-level component
80+
Yatagan$Outer$Inner // Nested component
81+
```
82+
83+
**After (2.0):**
84+
```
85+
YataganMyComponent // Top-level component (no separator)
86+
YataganOuter_Inner // Nested component (underscore instead of $)
87+
```
88+
89+
**Impact:**
90+
- If you use `Yatagan.create()` or `Yatagan.builder()` APIs, no changes needed
91+
- If you reference generated class names directly (reflection, ProGuard rules), update them
92+
- The runtime loader tries the new name first, then falls back to legacy names for compatibility
93+
94+
---
95+
96+
### 3. Subcomponent Binding Forbidden
97+
98+
Direct binding/injection of subcomponent instances is no longer allowed. Use factory methods instead.
99+
100+
**Before (1.x.y):**
101+
```kotlin
102+
@Component
103+
interface ParentComponent {
104+
// Could inject child directly (discouraged but worked)
105+
val child: ChildComponent
106+
}
107+
108+
@Component(isRoot = false)
109+
interface ChildComponent
110+
```
111+
112+
**After (2.0):**
113+
```kotlin
114+
@Component(modules = [ParentModule::class])
115+
interface ParentComponent {
116+
// Use factory method
117+
fun createChild(): ChildComponent
118+
}
119+
120+
@Module(subcomponents = [ChildComponent::class])
121+
interface ParentModule
122+
123+
@Component(isRoot = false)
124+
interface ChildComponent {
125+
@Component.Builder
126+
interface Builder {
127+
fun build(): ChildComponent
128+
}
129+
}
130+
```
131+
132+
---
133+
134+
### 4. Component Factory Method Behavior
135+
136+
All methods in a component interface returning a non-root component are now treated as factory methods, regardless of parameters.
137+
138+
**Before (1.x.y):**
139+
```kotlin
140+
@Component
141+
interface ParentComponent {
142+
fun createChild(dep: Dep): ChildComponent // Factory (has parameters)
143+
fun getChild(): ChildComponent // Entry-point (no parameters)
144+
}
145+
```
146+
147+
**After (2.0):**
148+
```kotlin
149+
@Component
150+
interface ParentComponent {
151+
fun createChild(dep: Dep): ChildComponent // Factory
152+
fun getChild(): ChildComponent // ALSO a factory now!
153+
}
154+
```
155+
156+
Review any parameterless methods returning child components.
157+
158+
---
159+
160+
### 5. Reflection Backend Configuration
161+
162+
The programmatic configuration API has been replaced with a properties file.
163+
164+
**Before (1.x.y):**
165+
```kotlin
166+
// In api:dynamic, Yatagan object had setup methods:
167+
Yatagan.setupReflectionBackend()
168+
.validation(SimpleDynamicValidationDelegate())
169+
.maxIssueEncounterPaths(5)
170+
.strictMode(true)
171+
.apply()
172+
```
173+
174+
**After (2.0):**
175+
176+
Create a properties file at `src/main/resources/META-INF/com.yandex.yatagan.reflection/parameters.properties`:
177+
178+
```properties
179+
validationDelegateClass=com.yandex.yatagan.rt.support.SimpleDynamicValidationDelegate
180+
maxIssueEncounterPaths=5
181+
enableStrictMode=true
182+
usePlainOutput=false
183+
enableDaggerCompatibility=false
184+
threadCheckerClassName=com.example.MyThreadAssertions
185+
```
186+
187+
The unified `Yatagan` entry point in `api:public` automatically detects and uses the reflection backend when:
188+
1. The `api:dynamic` module is on the classpath
189+
2. No compiled implementation is found
190+
191+
#### DynamicValidationDelegate Interface Changes
192+
193+
If you have a custom `DynamicValidationDelegate` implementation:
194+
195+
1. **Constructor requirement**: Custom delegates referenced in `parameters.properties` must have a **no-arg constructor** (instantiated via reflection)
196+
197+
2. **Logger property**: Now required to implement `logger` property (previously set via builder):
198+
```kotlin
199+
override val logger: Logger? = ...
200+
```
201+
202+
3. **Reporting methods signature change**:
203+
```kotlin
204+
// Before (1.x.y)
205+
override fun reportError(message: RichString)
206+
override fun reportWarning(message: RichString)
207+
208+
// After (2.0)
209+
override fun reportError(message: String)
210+
override fun reportWarning(message: String)
211+
```
212+
213+
4. **Removed builder method**: `reportDuplicateAliasesAsErrors()` removed from programmatic API (now always enabled)
214+
215+
---
216+
217+
### 6. Thread Assertions API Redesign
218+
219+
The global `Yatagan.setThreadAsserter()` API has been removed. Thread assertions are now configured at compile-time via a processor option.
220+
221+
**Before (1.x.y):**
222+
```kotlin
223+
// Runtime configuration via global state
224+
Yatagan.setThreadAsserter(MyThreadAsserter())
225+
// or
226+
ThreadAssertions.setAsserter(MyThreadAsserter())
227+
```
228+
229+
**After (2.0):**
230+
```kotlin
231+
// 1. Create a class with a static method or Kotlin object:
232+
object MyThreadAssertions {
233+
@JvmStatic
234+
fun assertThreadAccess() {
235+
// Your thread checking logic - throw if wrong thread
236+
check(Looper.myLooper() == Looper.getMainLooper()) {
237+
"Must be called on main thread"
238+
}
239+
}
240+
}
241+
242+
// 2. Configure via processor option in build.gradle.kts:
243+
ksp {
244+
arg("yatagan.threadCheckerClassName", "com.example.MyThreadAssertions")
245+
}
246+
// or for KAPT:
247+
kapt {
248+
arguments {
249+
arg("yatagan.threadCheckerClassName", "com.example.MyThreadAssertions")
250+
}
251+
}
252+
```
253+
254+
**Important:** The class does NOT need to implement any interface. Requirements for the method:
255+
- Must be named `assertThreadAccess`
256+
- Must be either **static** or in a **Kotlin object**
257+
- Must be **public** or **internal**
258+
- Must have **no parameters**
259+
260+
If no `yatagan.threadCheckerClassName` is specified, thread checking is disabled (equivalent to the old `yatagan.experimental.omitThreadChecks=true`).
261+
262+
---
263+
264+
## Deprecated APIs
265+
266+
These APIs still work in 2.0 but are deprecated and will be removed in future versions.
267+
268+
### Legacy Conditions API
269+
270+
| Annotation | Replacement | Notes |
271+
|------------|-------------|-------|
272+
| `@Condition` | `@ConditionExpression` | Use expression syntax |
273+
| `@AnyCondition` | `@ConditionExpression` with `\|` | OR operator |
274+
| `@AllConditions` | `@ConditionExpression` with `&` | AND operator |
275+
| `@AnyConditions` | `@ConditionExpression` | Combined expressions |
276+
277+
**Before (legacy):**
278+
```kotlin
279+
@Condition(Flags::class, "INSTANCE.isFeatureEnabled")
280+
annotation class FeatureA
281+
282+
@AnyCondition(
283+
Condition(Flags::class, "INSTANCE.isDebug"),
284+
Condition(Flags::class, "INSTANCE.isTesting"),
285+
)
286+
annotation class DebugOrTest
287+
```
288+
289+
**After (recommended):**
290+
```kotlin
291+
@ConditionExpression("INSTANCE.isFeatureEnabled", Flags::class)
292+
annotation class FeatureA
293+
294+
@ConditionExpression("INSTANCE.isDebug | INSTANCE.isTesting", Flags::class)
295+
annotation class DebugOrTest
296+
```
297+
298+
### Module Structure
299+
300+
#### `api:compiled` is deprecated
301+
302+
The `api:compiled` module is now an empty wrapper that re-exports `api:public`. It will be removed in a future release.
303+
304+
```kotlin
305+
// Before
306+
implementation("com.yandex.yatagan:api-compiled:$version")
307+
308+
// After
309+
implementation("com.yandex.yatagan:api-public:$version")
310+
```
311+
312+
#### `api:common` is removed
313+
314+
The internal `api:common` module has been removed. If you depended on it (not recommended), switch to `api:public`.
315+
316+
---
317+
318+
## Processor Options
319+
320+
### Removed Options
321+
322+
| Option | Reason |
323+
|--------|--------|
324+
| `yatagan.experimental.reportDuplicateAliasesAsErrors` | Now always enabled |
325+
| `yatagan.experimental.omitThreadChecks` | Use `yatagan.threadCheckerClassName` (don't set = no checks) |
326+
327+
### New Options
328+
329+
| Option | Type | Default | Description |
330+
|--------|------|---------|-------------|
331+
| `yatagan.threadCheckerClassName` | String | null | Fully qualified class name with `assertThreadAccess()` method |
332+
| `yatagan.experimental.enableDaggerCompatibility` | Boolean | false | Enable Dagger compatibility mode |

0 commit comments

Comments
 (0)