001/* 002 * JDrupes Builder 003 * Copyright (C) 2025, 2026 Michael N. Lipp 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <https://www.gnu.org/licenses/>. 017 */ 018 019package org.jdrupes.builder.api; 020 021import java.io.IOException; 022import java.nio.file.Files; 023import java.nio.file.Path; 024import java.util.EnumSet; 025import java.util.Optional; 026import java.util.Set; 027import java.util.function.Function; 028import static org.jdrupes.builder.api.CoreProperties.*; 029 030/// [Project]s are used to structure the build configuration. Every build 031/// configuration has a single root project and may contain sub-projects. 032/// The root project serves as the entry point for the build. Resources 033/// provided by the builder are typically provided by the root project, 034/// which acts as the central access point of the build configuration. 035/// 036/// Projects are [ResourceProvider]s that obtain resources from related 037/// [ResourceProvider]s. Conceptually, a project acts as a router for 038/// requests and resources, with its behavior depending on the intended 039/// usage of the resources obtained from the providers registered as 040/// dependencies. The intended usage is indicated by the [Intent] that 041/// attributes the relationship between a project and its related 042/// resource providers. 043/// 044/// ## Attributing relationships to providers 045/// 046/// ### Intent Supply 047/// 048///  049/// 050/// Resources from a provider added with [Intent#Supply] are made available 051/// by the project to entities that depend on it. [Intent#Supply] implies 052/// that the resources are generated specifically for the project, 053/// typically by a [Generator] that belongs to the project. 054/// 055/// ### Intent Consume and Reveal 056/// 057///  058/// 059/// Resources from a provider added with [Intent#Consume] or 060/// [Intent#Reveal] are typically used only by a project's generators. 061/// If a provider is added with [Intent#Reveal], its resources are also 062/// provided by the project when they are explicitly included in a 063/// request. 064/// 065/// ### Intent Expose 066/// 067///  068/// 069/// Resources from a provider added with [Intent#Expose] (typically 070/// another project) are used by a project's generators and are also 071/// provided by the project to entities that depend on it. 072/// 073/// ### Intent Forward 074/// 075///  076/// 077/// Resources from a provider added with [Intent#Forward] (typically 078/// another project) are provided by the project to entities that depend 079/// on it. These resources are not intended to be used by the project's 080/// generators. This cannot be enforced, however, as generators may still 081/// access them via [Project#providers()]. 082/// 083/// ## Behavior as resource provider 084/// 085/// In its role as a [ResourceProvider], a [Project] 086/// [provides resources][ResourceProvider#resources] obtained from its 087/// dependencies. A [ResourceRequest] controls to which dependencies a 088/// request is forwarded by including the respective [Intent]s in the 089/// set returned by [ResourceRequest#uses]. 090/// 091/// Concepts from other build tools, such as Gradle’s dependency 092/// configurations, can be mapped to resource requests for classpath 093/// elements using sets of [Intent]s as follows: 094/// 095/// <table class="simple-table"> 096/// <thead> 097/// <tr> 098/// <th style="white-space: nowrap;">Intent \ Config.</th> 099/// <th>Api</th> 100/// <th>Implementation</th> 101/// <th>Compile only</th> 102/// <th>Runtime only</th> 103/// </tr> 104/// </thead> 105/// <tbody> 106/// <tr><td>Supply</td><td>X</td><td>X</td><td></td><td></td></tr> 107/// <tr><td>Consume</td><td></td><td></td><td>X</td><td></td></tr> 108/// <tr><td>Reveal</td><td></td><td>X</td><td></td><td></td></tr> 109/// <tr><td>Expose</td><td>X</td><td>X</td><td></td><td></td></tr> 110/// <tr><td>Forward</td><td></td><td></td><td></td><td>X</td></tr> 111/// </tbody> 112/// </table> 113/// 114/// To ensure consistent results, a project adjusts a request before 115/// forwarding it to a dependency of type [Project] (typically a 116/// sub-project). If the request uses `Consume` or `Expose`, `Consume` 117/// is removed and `Expose` and `Supply` are added. The reason is that, 118/// regardless of how a sub-project contributes to another project, the 119/// resources it contributes are always those that are part of its API. 120/// 121/// To avoid misuse of [Intent]s, all intents are removed from a request 122/// before it is forwarded to a dependency that is not a project. 123/// 124/// ## Factory method for resources 125/// 126/// As a convenience, this interface also defines a shortcut for creating 127/// [Resource]s. 128/// 129/// @startuml supply-demo.svg 130/// object "project: Project" as project 131/// object "dependant" as dependant 132/// dependant -right-> project 133/// object "generator: Generator" as generator 134/// project *-down-> generator: "<<Supply>>" 135/// @enduml 136/// 137/// @startuml expose-demo.svg 138/// object "project: Project" as project 139/// object "dependant" as dependant 140/// dependant -right-> project 141/// object "providing: Project" as providing 142/// project *-right-> providing: "<<Expose>>" 143/// object "generator: Generator" as generator 144/// project *-down-> generator: " " 145/// generator .up.> project: "provided" 146/// @enduml 147/// 148/// @startuml consume-demo.svg 149/// object "project: Project" as project 150/// object "dependant" as dependant 151/// dependant -right-> project 152/// object "providing: Project" as providing 153/// project *-right-> providing: "<<Consume>>" 154/// object "generator: Generator" as generator 155/// project *-down-> generator: " " 156/// generator .up.> project: "provided" 157/// @enduml 158/// 159/// @startuml forward-demo.svg 160/// object "project: Project" as project 161/// object "dependant" as dependant 162/// dependant -right-> project 163/// object "providing: Project" as providing 164/// project *-right-> providing: "<<Forward>>" 165/// object "generator: Generator" as generator 166/// project *-down-> generator 167/// @enduml 168/// 169public interface Project extends ResourceProvider { 170 171 /// Returns the root project. 172 /// 173 /// @return the project 174 /// 175 RootProject rootProject(); 176 177 /// Returns the instance of the given project class. Projects 178 /// are created lazily by the builder and must be accessed 179 /// via this method. 180 /// 181 /// @param project the requested project's type 182 /// @return the project 183 /// 184 Project project(Class<? extends Project> project); 185 186 /// Returns the parent project. The root project has no parent. 187 /// 188 /// @return the parent project 189 /// 190 Optional<Project> parentProject(); 191 192 /// Returns the project's directory. 193 /// 194 /// @return the path 195 /// 196 Path directory(); 197 198 /// Returns the project's name and its directory appended in parentheses. 199 /// Appending the directory is omitted if it's name equals the 200 /// project's name. 201 /// 202 /// @return the string 203 /// 204 default String nameWithDirectory() { 205 StringBuilder result = new StringBuilder(name()); 206 if (directory() != null) { 207 var relDir = rootProject().directory().relativize(directory()); 208 if (!relDir.toString().equals(name()) 209 && !relDir.toString().isEmpty()) { 210 result.append(" (in ").append(relDir).append(')'); 211 } 212 } 213 return result.toString(); 214 215 } 216 217 /// Returns the directory where the project's [Generator]s should 218 /// create the artifacts. This is short for 219 /// `directory().resolve(get(BuildDirectory))`. 220 /// 221 /// @return the path 222 /// 223 default Path buildDirectory() { 224 return directory().resolve(get(BuildDirectory)); 225 } 226 227 /// Adds a provider to the project that generates resources which 228 /// are then provided by the project. For "normal" projects, the 229 /// generated resources are assumed to be provided to dependents of 230 /// the project, so the invocation is shorthand for 231 /// `dependency(Intent.Supply, generator)`. 232 /// 233 /// For projects that implement [MergedTestProject], generated resources 234 /// are usually intended to be used by the project itself only, so 235 /// the invocation is short for `dependency(Intent.Consume, generator)`. 236 /// 237 /// @param generator the provider 238 /// @return the project 239 /// 240 Project generator(Generator generator); 241 242 /// Uses the supplier to create a provider, passing this project as 243 /// argument and adds the result as a generator to this project. This 244 /// is a convenience method to add a provider to the project by writing 245 /// (in a project's constructor): 246 /// 247 /// ```java 248 /// generator(Provider::new); 249 /// ``` 250 /// instead of: 251 /// 252 /// ```java 253 /// generator(new Provider(this)); 254 /// ``` 255 /// 256 /// @param <T> the generic type 257 /// @param supplier the supplier 258 /// @return the project for method chaining 259 /// 260 default <T extends Generator> T generator(Function<Project, T> supplier) { 261 var provider = supplier.apply(this); 262 generator(provider); 263 return provider; 264 } 265 266 /// Adds a provider that contributes resources to the project with 267 /// the given intended usage. 268 /// 269 /// While this could be used to add a [Generator] to the project 270 /// as a provider with [Intent#Supply], it is recommended to use 271 /// one of the "generator" methods for better readability. 272 /// 273 /// @param intent the dependency type 274 /// @param provider the provider 275 /// @return the project for method chaining 276 /// @see generator(Generator) 277 /// @see generator(Function) 278 /// 279 Project dependency(Intent intent, ResourceProvider provider); 280 281 /// Uses the supplier to create a provider, passing this project as 282 /// argument and adds the result as a dependency to this project. This 283 /// is a convenience method to add a provider to the project by writing 284 /// (in a project's constructor): 285 /// 286 /// ```java 287 /// dependency(intent, Provider::new); 288 /// ``` 289 /// instead of: 290 /// 291 /// ```java 292 /// dependency(intent, new Provider(this)); 293 /// ``` 294 /// 295 /// @param <T> the generic type 296 /// @param intent the intent 297 /// @param supplier the supplier 298 /// @return the project for method chaining 299 /// 300 default <T extends ResourceProvider> T dependency(Intent intent, 301 Function<Project, T> supplier) { 302 var provider = supplier.apply(this); 303 dependency(intent, provider); 304 return provider; 305 } 306 307 /// Return a provider selection without any restrictions. 308 /// 309 /// @return the provider selection 310 /// 311 ProviderSelection providers(); 312 313 /// Return a provider selection that is restricted to the given intents. 314 /// 315 /// @param intents the intents 316 /// @return the provider selection 317 /// 318 ProviderSelection providers(Set<Intent> intents); 319 320 /// Return a provider selection that is restricted to the given intents. 321 /// 322 /// @param intent the intent 323 /// @param intents the intents 324 /// @return the provider selection 325 /// 326 default ProviderSelection providers(Intent intent, Intent... intents) { 327 return providers(EnumSet.of(intent, intents)); 328 } 329 330 /// Short for `directory().relativize(other)`. 331 /// 332 /// @param other the other path 333 /// @return the relativized path 334 /// 335 default Path relativize(Path other) { 336 return directory().relativize(other); 337 } 338 339 /// Sets the given property to the given value. 340 /// 341 /// Note that there is no way to enforce at compile time that the 342 /// type of the value passed to `set` matches the type of the property. 343 /// An implementation must check this at runtime by verifying that the 344 /// given value is assignable to the default value. 345 /// 346 /// @param <T> the value's type 347 /// @param property the property 348 /// @param value the value 349 /// @return the project 350 /// 351 <T> Project set(PropertyKey<T> property, T value); 352 353 /// Returns value of the given property of the project. If the 354 /// property is not set, the parent project's value is returned. 355 /// If neither is set, the property's default value is returned. 356 /// 357 /// @param <T> the values type 358 /// @param property the property 359 /// @return the property 360 /// 361 <T> T get(PropertyKey<T> property); 362 363 /// Convenience method for reading the content of a file into a 364 /// [String]. The path is resolved against the project's directory. 365 /// 366 /// @param path the path 367 /// @return the string 368 /// 369 default String readString(Path path) { 370 try { 371 return Files.readString(directory().resolve(path)); 372 } catch (IOException e) { 373 throw new BuildException().from(this).cause(e); 374 } 375 } 376}