Skip to content

PLASMA-8120: feat(sdds-core/theme-builder): per-platform generate tasks - #920

Merged
raininforest merged 9 commits into
developfrom
feature/ds-builder-generate
Sep 18, 2026
Merged

raininforest merged 9 commits into
developfrom
feature/ds-builder-generate

Conversation

@raininforest

@raininforest raininforest commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator
  • В plasma-android добавлены отдельные Gradle-таски generateComposeTheme/generateViewTheme/generateComposeComponents/generateViewComponents для генерации ровно одной платформы (плюс автоформатирование результата через spotlessApply), и локальный .sdds/components заработал как источник компонентов вместо только remote-пакета. Это разблокировало новый Android-делегат в CLI dsbuilder, который теперь умеет выполнять theme generate/components generate --platform compose|android-view, находя gradlew и запуская нужную таску напрямую.

ПР в ds-builder salute-developers/design-system-builder#74

@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 15925ab9-d9a3-4b9a-8b6f-8fdde4735b16

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@raininforest raininforest changed the title feat(sdds-core/theme-builder): per-platform generate tasks PLASMA-8120: feat(sdds-core/theme-builder): per-platform generate tasks Sep 16, 2026
… local components source

Adds generateComposeTheme/generateViewTheme/generateComposeComponents/
generateViewComponents alongside the existing generateTheme/
generateComponents, each generating exactly one platform and registered
only when the module actually configures it. This unblocks an external
caller (the dsbuilder CLI's Android platform delegate) that needs to
request one platform and get a clear "task not found" instead of a
silent wrong-platform generation.

- ThemeBuilderPlugin: registerThemeGenerator/registerGenerateComponentsTask
  take target as an explicit parameter instead of always reading
  extension.target; existing generateTheme/generateComponents calls are
  unchanged (same name, same type, same target incl. ALL).
- SddsComponentsSourceReader: local .sdds/components/meta.json fallback
  for dsBuilder.components, mirroring the existing theme fallback.
  registerFetchAndUnzipComponents skips fetch/unzip for local sources.
- Generate tasks are finalizedBy(spotlessApply) when present, with
  mustRunAfter declared from the spotless* tasks themselves — Gradle's
  implicit-dependency validation doesn't accept finalizedBy alone here,
  since spotlessKotlin reads the whole src/main/kotlin tree including
  freshly generated files.
- Syncs the ds-builder-gradle-dsl spec and adds the new
  components-builder-dsbuilder-source capability spec; archives the
  dsbuilder-cli-integration OpenSpec change.
…egation tasks

Review feedback on the CLI delegate (design-system-builder#74) pointed
out that the same "request a platform the module didn't configure and
get a clear error" reasoning behind the per-platform theme/components
tasks applies equally to documentationAggregate, which today is a
single task per module that silently picks Compose over View.

Adds aggregateComposeDocumentation/aggregateViewDocumentation,
registered only when that platform's target and documentation.compose/
documentation.view resolve, each pinned to its own componentsInfoFile/
themeInfoFile. documentationExtract is shared (code-snippet extraction
isn't platform-specific). documentationAggregate itself is unchanged:
same name, same Compose-over-View priority, still the only one wired
into preBuild/autoGenerate.
…tionable

FontTokenValidator reported duplicate fontWeight/fontStyle combinations with
a bare "has fontWeight and fontStyle duplicates" message that named neither
the font family nor which files collided. Include the family name and the
colliding file names per duplicate weight/style pair.

Also move the duplicate check out of the per-font forEach loop — it
re-scanned the whole font list from scratch on every iteration (O(n^2) for
no reason), now it runs once after the loop.
… generated theme name

Theme generation names the theme object from .sdds/config.json's tenant
publicName (SddsThemeSourceReader.baseAlias), while component generation
fell back to .sdds/components/meta.json's own "name" field when no explicit
componentSource was configured. When the tenant's public name differs from
the components package name (e.g. tenant "init_default" vs components
"init"), generated component styles end up referencing a theme object
(<name>Theme) that was never generated — every style file fails to compile
with "Unresolved reference".

SddsComponentsSourceReader.read() now accepts an optional themeNameOverride;
ComponentsCapability.toLegacyExtension resolves it from the same
SddsThemeSourceReader the theme capability uses, falling back to the old
meta.json-derived name if the theme config is missing or unreadable (so
components-only projects, with no theme block, keep working).
…mentation pipeline

Two independent problems surfaced only when the full generateTheme ->
generateComponents -> documentationExtract -> documentationAggregate chain
ran together under configuration cache:

- documentationExtract reads src/main/kotlin and src/main/theme-builder-res
  directly via project.file(...), with no declared dependency on the tasks
  that write them. Gradle can't infer the ordering itself, and with
  configuration cache enabled this is a hard "implicit dependency" failure
  instead of a warning. Order it explicitly with mustRunAfter against both
  generator task types (tasks.withType — safe even when a capability is
  disabled and its task was never registered).

- ExtractCodeSnippetsTask.compile() called project.layout.projectDirectory
  from inside @TaskAction — Task.project access at execution time is
  unsupported under configuration cache. Snapshot it into a DirectoryProperty
  at configuration time instead and read that at execution time.
…ly optional

screenshotsDirectory and userDocumentationRoot on DocumentationAggregateTask
are declared @optional @InputDirectory, and aggregate() already treats a
missing value as "no override docs" via .orNull. But registerAggregate()
always set them to the convention path regardless of whether it existed on
disk, and Gradle validates any *set* @InputDirectory value for existence
even when the property is optional — so a project with no override-docs/
directory (the common case) failed to build.

Only set the property when the directory is actually present; otherwise
leave it unset, which is exactly what the optional/.orNull handling already
expects.

Updated the plugin test fixture to create override-docs/ since it's now
asserting on the convention path resolving to a real directory.
…json and change theme defaults

dsBuilder.targets defaulted to an empty set, requiring every project to
repeat targets { compose() } even though .sdds/config.json already records
which platforms the project generates for (its "platforms" array, written
by the DS Builder CLI). Add SddsThemeSourceReader.readPlatforms(), mapping
"compose"/"android-view" to DsBuilderPlatform, and wire it as the
convention for extension.targets. It's a genuine default: an explicit
targets { } block in the build script still fully replaces it (standard
Gradle Property/SetProperty convention semantics — add/addAll supersede the
convention entirely on first call), and a missing or unreadable config.json
resolves to an empty set exactly like before.

Also change two theme defaults that made sense while the generator was
young but don't match how projects are actually configured today:
- mode: TOKENS_ONLY -> THEME
- ignoreDisabledTokens: false -> true
…h configuration cache

GeneratorFactory and the generators it builds (themeGenerator,
dimensGenerator, themeInfoGenerator, ...) held live DOM nodes
(XmlResourcesDocumentBuilder) that configuration cache can't serialize, but
they were stored as by unsafeLazy task fields — evaluated once and kept
alive for the task's lifetime. Move factory/generator construction into
local variables computed inside generate() instead, so nothing
non-serializable is retained as task state between configuration and
execution.

palette follows the same pattern (local val instead of a lazy field) but
stays where it's actually first used, right before createColorGenerator/
createShadowGenerator — matching the original lazy-evaluation point. Reading
it any earlier makes a missing/invalid palette file mask earlier, more
specific validation errors (missing metaFile, missing default tenant,
missing tenant color/gradient/... files), which is what the existing test
suite for this task checks message-for-message.
…om.ds.builder

DEFAULT_KT_PACKAGE was com.themebuilder.tokens, used whenever a project
doesn't set dsBuilder.packageName explicitly.
@raininforest
raininforest force-pushed the feature/ds-builder-generate branch from 9eead16 to 74f0a26 Compare September 18, 2026 11:40
@raininforest
raininforest merged commit 41f5d46 into develop Sep 18, 2026
19 checks passed
@raininforest
raininforest deleted the feature/ds-builder-generate branch September 18, 2026 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants