Skip to content

Commit c90b094

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

1 file changed

Lines changed: 323 additions & 0 deletions

File tree

MIGRATION.md

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
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+
- Compiled backends generate `Dagger<ComponentName>` bridge classes alongside Yatagan implementations
26+
- Yatagan and Dagger annotations can coexist; Yatagan takes priority when both present
27+
- Reflection backend also supports Dagger-compatible graphs via `enableDaggerCompatibility=true`, but uses the usual `Yatagan.create()` / `Yatagan.builder()` entry points
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))
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+
fun provideFoo(): Foo = FooImpl()
67+
}
68+
```
69+
70+
---
71+
72+
### 2. Generated Component Class Names
73+
74+
Generated implementation class names have changed to be more user-friendly, so that they can be used directly.
75+
76+
**Before (1.x.y):**
77+
```
78+
Yatagan$MyComponent // Top-level component
79+
Yatagan$Outer$Inner // Nested component
80+
```
81+
82+
**After (2.0):**
83+
```
84+
YataganMyComponent // Top-level component (no separator)
85+
YataganOuter_Inner // Nested component (underscore instead of $)
86+
```
87+
88+
**Impact:**
89+
- If you use `Yatagan.create()` or `Yatagan.builder()` APIs, no changes needed
90+
- If you reference generated class names directly (reflection, ProGuard rules), update them
91+
- The runtime loader tries the new name first, then falls back to legacy names for compatibility
92+
93+
---
94+
95+
### 3. Subcomponent Access Uses Factory Methods
96+
97+
Direct binding/injection of subcomponent instances is no longer allowed. In addition, every abstract component method
98+
returning a non-root component is now treated as a factory method, even when it has no parameters.
99+
100+
**Before (1.x.y):**
101+
```kotlin
102+
@Component(modules = [ParentModule::class])
103+
interface ParentComponent {
104+
val child: ChildComponent // Direct binding
105+
fun getChild(): ChildComponent // Entry-point in 1.x.y
106+
}
107+
108+
@Module(subcomponents = [ChildComponent::class])
109+
interface ParentModule
110+
111+
@Component(isRoot = false)
112+
interface ChildComponent
113+
```
114+
115+
**After (2.0):**
116+
```kotlin
117+
@Component(modules = [ParentModule::class])
118+
interface ParentComponent {
119+
fun createChild(): ChildComponent // Factory method
120+
}
121+
122+
@Module(subcomponents = [ChildComponent::class])
123+
interface ParentModule
124+
125+
@Component(isRoot = false)
126+
interface ChildComponent
127+
```
128+
129+
**Impact:**
130+
- Review any parameterless methods returning child components: in 2.0 they are factories, not entry-points
131+
- If a child component declares an explicit `@Component.Builder`, expose or inject `ChildComponent.Builder` instead of `ChildComponent`
132+
133+
---
134+
135+
### 4. Reflection Backend Configuration
136+
137+
The programmatic reflection-specific API has been replaced with an optional properties file.
138+
139+
**Before (1.x.y):**
140+
```kotlin
141+
// In api:dynamic, Yatagan object had setup methods:
142+
Yatagan.setupReflectionBackend()
143+
.validation(SimpleDynamicValidationDelegate())
144+
.maxIssueEncounterPaths(5)
145+
.strictMode(true)
146+
.apply()
147+
```
148+
149+
**After (2.0):**
150+
151+
If you need to customize the reflection backend, create a properties file at
152+
`src/main/resources/META-INF/com.yandex.yatagan.reflection/parameters.properties`:
153+
154+
```properties
155+
validationDelegateClass=com.yandex.yatagan.rt.support.SimpleDynamicValidationDelegate
156+
maxIssueEncounterPaths=5
157+
enableStrictMode=true
158+
usePlainOutput=false
159+
enableDaggerCompatibility=false
160+
threadCheckerClassName=com.example.MyThreadAssertions
161+
```
162+
163+
The file is optional. Without it, reflection still works and now validates graphs by default using
164+
`SimpleDynamicValidationDelegate`.
165+
166+
The old runtime methods `setupReflectionBackend()` and `resetReflectionBackend()` are gone.
167+
`useCompiledImplementationIfAvailable()` is no longer configurable, because the unified `Yatagan` entry point always
168+
tries compiled implementations first. `allConditionsLazy()` also no longer has a reflection-specific replacement.
169+
170+
The unified `Yatagan` entry point in `api:public` automatically detects and uses the reflection backend when:
171+
1. The `api:dynamic` module is on the classpath
172+
2. No compiled implementation is found
173+
174+
#### DynamicValidationDelegate Interface Changes
175+
176+
If you have a custom `DynamicValidationDelegate` implementation:
177+
178+
1. **Constructor requirement**: Custom delegates referenced in `parameters.properties` must have a **no-arg constructor** (instantiated via reflection)
179+
180+
2. **Logger property**: Now required to implement `logger` property (previously set via builder):
181+
```kotlin
182+
override val logger: Logger? = ...
183+
```
184+
185+
3. **Reporting methods signature change**:
186+
```kotlin
187+
// Before (1.x.y)
188+
override fun reportError(message: RichString)
189+
override fun reportWarning(message: RichString)
190+
191+
// After (2.0)
192+
override fun reportError(message: String)
193+
override fun reportWarning(message: String)
194+
```
195+
196+
4. **Behavior change**: reflection no longer has a validation-less mode; duplicate alias conflicts are now always reported as errors
197+
198+
---
199+
200+
### 5. Thread Assertions API Redesign
201+
202+
The global `Yatagan.setThreadAsserter()` API has been removed. Thread assertions are now configured at compile-time via a processor option.
203+
204+
**Before (1.x.y):**
205+
```kotlin
206+
// Runtime configuration via global state
207+
Yatagan.setThreadAsserter(MyThreadAsserter())
208+
// or
209+
ThreadAssertions.setAsserter(MyThreadAsserter())
210+
```
211+
212+
**After (2.0):**
213+
```kotlin
214+
// 1. Create a class with a static method or Kotlin object:
215+
object MyThreadAssertions {
216+
@JvmStatic
217+
fun assertThreadAccess() {
218+
// Your thread checking logic - throw if wrong thread
219+
check(Looper.myLooper() == Looper.getMainLooper()) {
220+
"Must be called on main thread"
221+
}
222+
}
223+
}
224+
225+
// 2. Configure via processor option in build.gradle.kts:
226+
ksp {
227+
arg("yatagan.threadCheckerClassName", "com.example.MyThreadAssertions")
228+
}
229+
// or for KAPT:
230+
kapt {
231+
arguments {
232+
arg("yatagan.threadCheckerClassName", "com.example.MyThreadAssertions")
233+
}
234+
}
235+
```
236+
237+
**Important:** The class does NOT need to implement any interface. Requirements for the method:
238+
- Must be named `assertThreadAccess`
239+
- Must be either **static** or in a **Kotlin object**
240+
- Must be **public** or **internal**
241+
- Must have **no parameters**
242+
243+
For reflection backend, set the same class name in `parameters.properties` via `threadCheckerClassName=...`.
244+
245+
If no `yatagan.threadCheckerClassName` is specified, thread checking is disabled (equivalent to the old `yatagan.experimental.omitThreadChecks=true`).
246+
247+
---
248+
249+
## Deprecated APIs
250+
251+
These APIs still work in 2.0 but are deprecated and will be removed in future versions.
252+
253+
### Legacy Conditions API
254+
255+
| Annotation | Replacement | Notes |
256+
|------------|-------------|-------|
257+
| `@Condition` | `@ConditionExpression` | Use expression syntax |
258+
| `@AnyCondition` | `@ConditionExpression` with `\|` | OR operator |
259+
| `@AllConditions` | `@ConditionExpression` with `&` | AND operator |
260+
| `@AnyConditions` | `@ConditionExpression` | Combined expressions |
261+
262+
**Before (legacy):**
263+
```kotlin
264+
@Condition(Flags::class, "INSTANCE.isFeatureEnabled")
265+
annotation class FeatureA
266+
267+
@AnyCondition(
268+
Condition(Flags::class, "INSTANCE.isDebug"),
269+
Condition(Flags::class, "INSTANCE.isTesting"),
270+
)
271+
annotation class DebugOrTest
272+
```
273+
274+
**After (recommended):**
275+
```kotlin
276+
@ConditionExpression("INSTANCE.isFeatureEnabled", Flags::class)
277+
annotation class FeatureA
278+
279+
@ConditionExpression("INSTANCE.isDebug | INSTANCE.isTesting", Flags::class)
280+
annotation class DebugOrTest
281+
```
282+
283+
### Module Structure
284+
285+
#### `api:compiled` is deprecated
286+
287+
The `api:compiled` module is now an empty wrapper that re-exports `api:public`. It will be removed in a future release.
288+
289+
```kotlin
290+
// Before
291+
implementation("com.yandex.yatagan:api-compiled:$version")
292+
293+
// After
294+
implementation("com.yandex.yatagan:api-public:$version")
295+
```
296+
297+
#### `api:common` is removed
298+
299+
The internal `api:common` module has been removed. If you depended on it (not recommended), switch to `api:public`.
300+
301+
#### Validation/plugin API packages
302+
303+
If you implement validation plugins or other integrations against Yatagan's model/graph APIs, note that helper types
304+
such as `Extensible`, `WithChildren`, and `WithParents` moved from `com.yandex.yatagan.core.graph` to
305+
`com.yandex.yatagan.base.api`.
306+
307+
---
308+
309+
## Processor Options
310+
311+
### Removed Options
312+
313+
| Option | Reason |
314+
|--------|--------|
315+
| `yatagan.experimental.reportDuplicateAliasesAsErrors` | Now always enabled |
316+
| `yatagan.experimental.omitThreadChecks` | Use `yatagan.threadCheckerClassName` (don't set = no checks) |
317+
318+
### New Options
319+
320+
| Option | Type | Default | Description |
321+
|--------|------|---------|-------------|
322+
| `yatagan.threadCheckerClassName` | String | null | Fully qualified class name with `assertThreadAccess()` method |
323+
| `yatagan.experimental.enableDaggerCompatibility` | Boolean | false | Enable Dagger compatibility mode |

0 commit comments

Comments
 (0)