|
| 1 | +/* |
| 2 | + * Copyright 2014-2025 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. |
| 3 | + */ |
| 4 | + |
| 5 | +package ktorbuild |
| 6 | + |
| 7 | +import ktorbuild.ProjectTagsService.Companion.projectTagsService |
| 8 | +import org.gradle.api.Project |
| 9 | +import org.gradle.api.provider.MapProperty |
| 10 | +import org.gradle.api.provider.Provider |
| 11 | +import org.gradle.api.services.BuildService |
| 12 | +import org.gradle.api.services.BuildServiceParameters |
| 13 | +import org.gradle.kotlin.dsl.getByType |
| 14 | +import org.gradle.kotlin.dsl.registerIfAbsent |
| 15 | + |
| 16 | +/** |
| 17 | + * Service allowing to aggregate projects by [ProjectTag] attached to it |
| 18 | + * in a way (potentially) compatible with "isolated projects" feature. |
| 19 | + * |
| 20 | + * Usage: |
| 21 | + * ``` |
| 22 | + * // In some subproject |
| 23 | + * addProjectTag(ProjectTag.Library) |
| 24 | + * |
| 25 | + * // In a project aggregating libraries |
| 26 | + * val libraryProjects = projectsWithTag(ProjectTag.Library) // Provider<List<Project>> |
| 27 | + * ``` |
| 28 | + * |
| 29 | + * @see addProjectTag |
| 30 | + * @see projectsWithTag |
| 31 | + */ |
| 32 | +abstract class ProjectTagsService : BuildService<BuildServiceParameters.None> { |
| 33 | + |
| 34 | + internal abstract val projectTags: MapProperty<String, Set<ProjectTag>> |
| 35 | + |
| 36 | + private val Project.tags: Set<ProjectTag> |
| 37 | + get() = projectTags.getting(path).orNull.orEmpty() |
| 38 | + |
| 39 | + internal fun addTag(project: Project, tag: ProjectTag) { |
| 40 | + projectTags.put(project.path, project.tags + tag) |
| 41 | + } |
| 42 | + |
| 43 | + internal fun hasTag(project: Project, tag: ProjectTag): Boolean = tag in project.tags |
| 44 | + |
| 45 | + internal fun getTagged(tag: ProjectTag): Set<String> { |
| 46 | + projectTags.finalizeValue() |
| 47 | + return projectTags.get().filterValues { tag in it }.keys |
| 48 | + } |
| 49 | + |
| 50 | + companion object { |
| 51 | + private const val NAME = "subprojectService" |
| 52 | + |
| 53 | + internal val Project.projectTagsService: ProjectTagsService |
| 54 | + get() = project.extensions.getByType<ProjectTagsService>() |
| 55 | + |
| 56 | + fun register(project: Project) { |
| 57 | + val service = project.gradle.sharedServices.registerIfAbsent(NAME, ProjectTagsService::class).get() |
| 58 | + project.extensions.add(NAME, service) |
| 59 | + } |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +/** Adds the specified [tag] to this project. */ |
| 64 | +fun Project.addProjectTag(tag: ProjectTag) { |
| 65 | + projectTagsService.addTag(this, tag) |
| 66 | +} |
| 67 | + |
| 68 | +/** |
| 69 | + * Returns lazy property collecting list of projects marked with the specified [tag]. |
| 70 | + * |
| 71 | + * Warning: Calling [Provider.get] evaluates all projects, so it should be done only when needed. |
| 72 | + * For example, if you need to add all projects with tag `Library` as "api" dependency to your project, |
| 73 | + * prefer lazy API to do so: |
| 74 | + * |
| 75 | + * ``` |
| 76 | + * val libraryProjects = projectsWithTag(ProjectTag.Library) |
| 77 | + * |
| 78 | + * // Eager API (evaluates all project immediately) |
| 79 | + * dependencies { |
| 80 | + * libraryProjects.get().forEach { api(it) } |
| 81 | + * } |
| 82 | + * |
| 83 | + * // Lazy API (evaluates all project only when 'api' configuration is needed) |
| 84 | + * configurations.api { |
| 85 | + * dependencies.addAllLater(libraryProjects.mapValue { project.dependencies.create(it) }) |
| 86 | + * } |
| 87 | + * ``` |
| 88 | + * |
| 89 | + * Implicitly adds tag [ProjectTag.Meta] to this project. |
| 90 | + */ |
| 91 | +fun Project.projectsWithTag(tag: ProjectTag): Provider<List<Project>> = projectsWithTag(tag) { it } |
| 92 | + |
| 93 | +/** |
| 94 | + * Returns lazy property collecting list of projects marked with the specified [tag] with [transform] applied to them. |
| 95 | + */ |
| 96 | +fun <T> Project.projectsWithTag(tag: ProjectTag, transform: (Project) -> T): Provider<List<T>> { |
| 97 | + addProjectTag(ProjectTag.Meta) |
| 98 | + |
| 99 | + // Postpone projects evaluation and tags freezing |
| 100 | + return provider { |
| 101 | + ensureAllProjectsEvaluated() |
| 102 | + projectTagsService.getTagged(tag).map { transform(project(it)) } |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +private fun Project.ensureAllProjectsEvaluated() { |
| 107 | + val service = projectTagsService |
| 108 | + |
| 109 | + for (subproject in rootProject.subprojects) { |
| 110 | + if (subproject == this || service.hasTag(subproject, ProjectTag.Meta)) continue |
| 111 | + evaluationDependsOn(subproject.path) |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | +enum class ProjectTag { |
| 116 | + /** Project published to the Maven repository. */ |
| 117 | + Published, |
| 118 | + |
| 119 | + /** Public library project. Implies [Published]. */ |
| 120 | + Library, |
| 121 | + |
| 122 | + /** Project containing JVM target. Implies [Library]. */ |
| 123 | + Jvm, |
| 124 | + |
| 125 | + /** Project aggregating information about other projects. */ |
| 126 | + Meta, |
| 127 | +} |
| 128 | + |
| 129 | + |
0 commit comments