From a4f5283f5c651023b3260feb543a12b927e144f9 Mon Sep 17 00:00:00 2001 From: Luke Bemish Date: Mon, 3 Feb 2025 16:56:12 -0600 Subject: [PATCH] Initial refactors for single-scope configurations and shared fabric installations --- build.gradle | 15 +- codedocs/configurations.md | 862 +++++++++++++++ config/checkstyle/checkstyle.xml | 49 + config/checkstyle/suppressions.xml | 6 + gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle | 2 +- .../lukebemish/crochet/CrochetProperties.java | 3 + .../crochet/internal/ConfigurationUtils.java | 428 +++++++- .../internal/CrochetProjectPlugin.java | 56 +- .../crochet/internal/ExtensionHolder.java | 14 + .../crochet/internal/FileListStringifier.java | 1 - .../MappingsConfigurationCounter.java | 14 +- .../lukebemish/crochet/internal/Memoize.java | 94 ++ .../crochet/internal/NameProvider.java | 21 + .../crochet/internal/NameUtils.java | 20 + .../crochet/internal/ProjectHolder.java | 14 + .../crochet/internal/SimpleProblemGroup.java | 34 + .../crochet/internal/TaskUtils.java | 41 + .../internal/metadata/package-info.java | 6 + .../crochet/internal/package-info.java | 2 + .../internal/tasks/MakeRunDirectories.java | 32 + .../AbstractExternalVanillaInstallation.java | 10 - .../model/AbstractVanillaInstallation.java | 67 +- .../crochet/model/CrochetExtension.java | 77 +- .../model/CrochetSettingsExtension.java | 5 +- .../ExternalAbstractVanillaInstallation.java | 13 - .../model/ExternalFabricInstallation.java | 239 +++++ .../model/ExternalMinecraftInstallation.java | 24 +- .../model/ExternalVanillaInstallation.java | 8 +- .../crochet/model/FabricDependencyBundle.java | 2 +- .../crochet/model/FabricInstallation.java | 942 +++++------------ .../model/FabricInstallationDependencies.java | 6 +- .../model/FabricInstallationLogic.java | 999 ++++++++++++++++++ .../model/FabricRemapDependencies.java | 16 +- .../model/FabricSourceSetDependencies.java | 2 +- .../model/LocalMinecraftInstallation.java | 186 ++-- .../lukebemish/crochet/model/Mappings.java | 12 +- .../crochet/model/MinecraftInstallation.java | 97 +- .../crochet/model/NeoFormInstallation.java | 116 +- .../dev/lukebemish/crochet/model/Run.java | 7 +- .../model/SettingsMinecraftInstallation.java | 49 +- .../crochet/model/VanillaInstallation.java | 6 +- ...gic.java => VanillaInstallationLogic.java} | 9 +- test/build.gradle | 29 +- test/consumes-shared/build.gradle | 6 +- test/gradle.properties | 6 + test/gradle/wrapper/gradle-wrapper.properties | 2 +- test/settings.gradle | 20 +- test/src/yarn/java/test/Test.java | 3 + test/subproject/build.gradle | 4 +- tools/build.gradle | 5 +- .../tools/TransformAccessWideners.java | 2 +- 52 files changed, 3555 insertions(+), 1130 deletions(-) create mode 100644 codedocs/configurations.md create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/checkstyle/suppressions.xml create mode 100644 src/main/java/dev/lukebemish/crochet/internal/ExtensionHolder.java create mode 100644 src/main/java/dev/lukebemish/crochet/internal/Memoize.java create mode 100644 src/main/java/dev/lukebemish/crochet/internal/NameProvider.java create mode 100644 src/main/java/dev/lukebemish/crochet/internal/NameUtils.java create mode 100644 src/main/java/dev/lukebemish/crochet/internal/ProjectHolder.java create mode 100644 src/main/java/dev/lukebemish/crochet/internal/SimpleProblemGroup.java create mode 100644 src/main/java/dev/lukebemish/crochet/internal/TaskUtils.java create mode 100644 src/main/java/dev/lukebemish/crochet/internal/metadata/package-info.java create mode 100644 src/main/java/dev/lukebemish/crochet/internal/tasks/MakeRunDirectories.java delete mode 100644 src/main/java/dev/lukebemish/crochet/model/AbstractExternalVanillaInstallation.java create mode 100644 src/main/java/dev/lukebemish/crochet/model/ExternalFabricInstallation.java create mode 100644 src/main/java/dev/lukebemish/crochet/model/FabricInstallationLogic.java rename src/main/java/dev/lukebemish/crochet/model/{VanillaInstallationRunLogic.java => VanillaInstallationLogic.java} (82%) diff --git a/build.gradle b/build.gradle index e77031a..49a0443 100644 --- a/build.gradle +++ b/build.gradle @@ -6,11 +6,12 @@ plugins { id 'java-gradle-plugin' id 'org.gradle.kotlin.kotlin-dsl' version '5.1.2' // Version for gradle 8.12 id 'maven-publish' + id 'checkstyle' id 'dev.lukebemish.managedversioning' alias cLibs.plugins.gradlepublish } -group='dev.lukebemish' +group = 'dev.lukebemish' managedVersioning { versionFile.set project.file('version.properties') @@ -149,8 +150,8 @@ repositories { mavenCentral() gradlePluginPortal() maven { - name 'Staging' - url 'https://maven.lukebemish.dev/staging/' + name = 'Staging' + url = 'https://maven.lukebemish.dev/staging/' } } @@ -192,8 +193,12 @@ java { tasks.withType(AbstractArchiveTask) { preserveFileTimestamps = false reproducibleFileOrder = true - dirMode = 0755 - fileMode = 0644 + dirPermissions { + unix 0755 + } + filePermissions { + unix 0644 + } } abstract class DownloadManifest extends DefaultTask { diff --git a/codedocs/configurations.md b/codedocs/configurations.md new file mode 100644 index 0000000..38ee2a3 --- /dev/null +++ b/codedocs/configurations.md @@ -0,0 +1,862 @@ +# Installation-created Configurations + +General rules: +- Configurations starting with `_crochet` are meant to be internal; `*` is a placeholder for the (capitalized if necessary) parent name. +- Configurations exposed through a `Dependencies` should not be internal, and should have a name corresponding to their `DependencyCollector`. +- Configurations should be created through `dependencyScope`, `consumable`, or `resolvable` unless there is a documented reason otherwise. +- Configurations should be created through `ConfigurationUtils`, using the relevant helper methods, to ensure consistency + +Note: this document is used by `ConfigurationUtils` in development to validate that created configurations are documented. +Configurations must have the role they are documented as having here, and must be included in this document. + +## CrochetProjectPlugin + +### `*LocalRuntime` +`dependencyScope` + +Local runtime dependencies that contribute to the runtime classpath but are not published. + +### `*LocalImplementation` +`dependencyScope` + +Local implementation dependencies that contribute to the runtime and compile classpaths but are not published. + +### `_crochet*TaskGraphRunner` +`dependencyScope` + +Dependencies for TaskGraphRunner. + +### `_crochet*TaskGraphRunnerClasspath` +`resolvable` extends `_crochet*TaskGraphRunner` + +Contains the classpath of TaskGraphRunner. + +Attributes: +- `org.gradle.jvm.version` - 21 +- `org.gradle.dependency.bundling` - `shadowed` + +### `_crochet*TaskGraphRunnerTools` +`dependencyScope` + +Contains tools TaskGraphRunner may invoke. + +### `_crochet*TaskGraphRunnerToolsClasspath` +`resolvable` extends `_crochet*TaskGraphRunnerTools` + +Contains the classpath of tools TaskGraphRunner may invoke. + +Attributes: +- `org.gradle.jvm.version` - 21 + +### `_crochet*DevLaunch` +`dependencyScope` + +Contains neo's DevLaunch tool, for runs. + +### `_crochet*TerminalConsoleAppender` +`dependencyScope` + +Contains terminalconsoleappender, for runs. + +## MappingsConfigurationCounter + +### `_crochet*CounterMappings` +`dependencyScope` + +Depends on a single set of mappings within the mappings DSL. + +### `_crochet*CounterMappingsClasspath` +`resolvable` extends `_crochet*CounterMappings` + +Contains a single set of mappings within the mappings DSL. + +## MinecraftInstallation + +### `*MinecraftDependencies` +`dependencyScope` + +Given the dependencies of Minecraft. + +### `_crochet*MinecraftDependenciesVersioning` +`resolvable` extends `*MinecraftDependencies` + +Produces the minecraft version of an installation by locating a +resolved variant with a given capability; thus, cannot depend on that version. Resolved _only_ to determine this version; +otherwise, consumers should use `*MinecraftDependencies`. + +Attributes: +- `net.neoforged.distribution` - Installation default +- `org.gradle.usage` - `java-api` + +### `_crochet*NonUpgradableDependencies` +`dependencyScope` extends `*MinecraftDependencies` + +Collects a set of dependencies who, along with their transitive dependencies, should be impossible to upgrade at compile +or runtime. Includes things that may have to upgrade MC's dependencies, such as loader. + +### `_crochet*NonUpgradableClientCompileVersioning` +`resolvable` extends `_crochet*NonUpgradableDependencies` + +Pins versioning for client compile dependencies. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.usage` - `java-api` + +### `_crochet*NonUpgradableServerCompileVersioning` +`resolvable` extends `_crochet*NonUpgradableDependencies` + +Pins versioning for server compile dependencies. + +Attributes: +- `net.neoforged.distribution` - `server` +- `org.gradle.usage` - `java-api` + +### `_crochet*NonUpgradableClientRuntimeVersioning` +`resolvable` extends `_crochet*NonUpgradableDependencies` + +Pins versioning for client runtime dependencies. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.usage` - `java-runtime` + +### `_crochet*NonUpgradableServerRuntimeVersioning` +`resolvable` extends `_crochet*NonUpgradableDependencies` + +Pins versioning for server runtime dependencies. + +Attributes: +- `net.neoforged.distribution` - `server` +- `org.gradle.usage` - `java-runtime` + +### `_crochet*MinecraftResources` +`dependencyScope` + +Contains the resources jar of an installation. + +### `_crochet*Minecraft` +`dependencyScope` + +Contains the runtime classpath of minecraft, including dependencies and the resource jar. + +### `_crochet*MinecraftLineMapped` +`dependencyScope` + +Contains the runtime classpath of minecraft, including dependencies and the resource jar, but with binaries having fixed +line mappings. + +## LocalMinecraftInstallation + +### `*AccessTransformers` +`dependencyScope` + +Access transformers an installation should consume. + +### `*AccessTransformersApi` +`dependencyScope` + +Access transformers an installation should consume, and expose to dependents. + +### `_crochet*AccessTransformersPath` +`resolvable` extends `*AccessTransformers`, `*AccessTransformersApi` + +Contains all access transformers to use for the installation. + +Attributes: +- `org.gradle.category` - `accesstransformer` + +### `*AccessTransformersElements` +`consumable` extends `*InjectedInterfacesApi` + +Access transformers to expose`to dependents. + +Attributes: +- `org.gradle.category` - `accesstransformer` + +### `*InjectedInterfaces` +`dependencyScope` + +Injected interfaces an installation should consume, as neo-format files. + +### `*InjectedInterfacesApi` +`dependencyScope` + +Injected interfaces an installation should consume, and expose to dependents, as neo-format files. + +### `_crochet*InjectedInterfacesPath` +`resolvable` extends `*InjectedInterfaces`, `*InjectedInterfacesApi` + +Contains all injected interfaces to use for the installation, as neo-format files. + +Attributes: +- `org.gradle.category` - `interfaceinjection` + +### `*InjectedInterfacesElements` +`consumable` extends `*InjectedInterfacesApi` + +Injected interfaces to expose to dependents, as neo-format files. + +Attributes: +- `org.gradle.category` - `interfaceinjection` + +### `_crochet*MinecraftElements` +`consumable` extends `*Minecraft` + +Contains the runtime classpath of minecraft, including dependencies and the resource jar, to +expose to externally-sourced installations when shared. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:minecraft-:1.0.0` + +### `_crochet*MinecraftDependenciesElements` +`consumable` extends `*MinecraftDependencies` + +Contains the dependencies of Minecraft, to expose to externally-sourced installations when shared. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:minecraft-dependencies-:1.0.0` + +### `_crochet*MinecraftLineMappedElements` +`consumable` extends `*MinecraftLineMapped` + +Contains the runtime classpath of minecraft, including dependencies and the resource jar, but with binaries having fixed +line mappings, to expose to externally-sourced installations when shared. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:minecraft-linemapped-:1.0.0` + +### `_crochet*MinecraftResourcesElements` +`consumable` extends `*MinecraftResources` + +Contains the resources jar of an installation, to expose to externally-sourced installations when shared. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:minecraft-resources-:1.0.0` + +### `_crochet*NonUpgradableElements` +`consumable` extends `_crochet*NonUpgradableDependencies` + +Contains non-upgradable dependencies, to expose to externally-sourced installations when shared. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:non-upgradable-:1.0.0` + +### `_crochet*AssetsProperties` +`dependencyScope` + +Contains the generated assets properties file of an installation. + +### `_crochet*AssetsPropertiesElements` +`consumable` extends `_crochet*AssetsProperties` + +Contains the generated assets properties file of an installation, to expose to externally-sourced installations when shared. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:assets-properties-:1.0.0` + +## AbstractVanillaInstallation + +### `_crochet*PistonMetaDownloads` +`dependencyScope` + +Contains stub dependencies capable of locating various things from piston-meta that TaskGraphRunner needs to produce a +remapped minecraft jar. + +### `_crochet*ClientJarPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the original client jar of the target version. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.category` - `library` + +Excludes: +- `dev.lukebemish.crochet.mojang-stubs:minecraft-dependencies` (not needed but brought in by the target stub variant) + +### `_crochet*ServerJarPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the original server jar of the target version. + +Attributes: +- `net.neoforged.distribution` - `server` +- `org.gradle.category` - `library` + +Excludes: +- `dev.lukebemish.crochet.mojang-stubs:minecraft-dependencies` (not needed but brought in by the target stub variant) + +### `_crochet*ClientMappingsPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the client mappings of the target version. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.category` - `mappings` + +### `_crochet*VersionJsonPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the version json of the target version. + +Attributes: +- `org.gradle.category` - `versionjson` + +### `_crochet*RunnerCompileClasspath` +`resolvable` extends `*MinecraftDependencies` + +Contains the compile resolution of the classpath used to decompile Minecraft by TaskGraphRunner. Resolves client +dependencies under the assumption that they are a superset of server, and since TaskGraphRunner is queried with `joined`. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.usage` - `java-api` + +### `_crochet*RunnerRuntimeClasspath` +`resolvable` extends `*MinecraftDependencies` + +Contains the runtime resolution of the classpath used to decompile Minecraft by TaskGraphRunner. Resolves client +dependencies under the assumption that they are a superset of server, and since TaskGraphRunner is queried with `joined`. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.usage` - `java-runtime` + +## FabricInstallation + +### `*Loader` +`dependencyScope` + +Contains the version of fabric loader being used. + +Extended by `_crochet*NonUpgradableDependencies` + +### `_crochet*LoaderElements` +`consumable` extends `*Loader` + +Contains the version of fabric loader being used, to expose to externally-sourced installations when shared. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:loader-:1.0.0` + +### `*AccessWideners` +`dependencyScope` + +Access wideners an installation should consume. + +### `*AccessWidenersElements` +`consumable` extends `*AccessWideners` + +Access wideners to expose to dependents. + +### `_crochet*AccessWidenersPath` +`resolvable` extends `*AccessWideners` + +Resolves standalone access widener files for the configuration. + +Attributes: +- `org.gradle.category` - `accesswidener` + +### `_crochet*SharedInjectedInterfacesElements` +`consumable` + +Contains injected-interface files that should be bundled into the jar with this configuration (the artifacts of +`*InjectedInterfacesElements`). + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:injected-interfaces-:1.0.0` + +### `_crochet*Intermediary` +`dependencyScope` + +Contains the intermediary mappings of the target version. + +### `_crochet*IntermediaryClasspath` +`resolvable` extends `_crochet*Intermediary` + +Contains the classpath of intermediary mappings of the target version. + +### `_crochet*Mappings` +`dependencyScope` + +Contains to total intermediary-to-named mappings. + +### `_crochet*MappingsClasspath` +`resolvable` extends `_crochet*Mappings` + +Resolves `_crochet*Mappings` + +### `_crochet*IntermediaryMinecraft` +`dependencyScope` + +Contains the minecraft jar in intermediary, and its dependencies. + +### `_crochet*IntermediaryMinecraftElements` +`consumable` extends `_crochet*IntermediaryMinecraft` + +Contains the minecraft jar in intermediary, and its dependencies, to expose to externally-sourced installations when shared. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:intermediary-:1.0.0` + +### `_crochet*IntermediaryToNamedMappings` +`consumable` + +Exposes the intermediary-to-named mappings for externally-sourced installations. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:mappings-intermediary-named-:1.0.0` + +### `_crochet*NamedToIntermediaryMappings` +`consumable` + +Exposes the named-to-intermediary mappings for externally-sourced installations. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.shared-:mappings-named-intermediary-:1.0.0` + +### `_crochet*CompileRemappedDependencies` +`dependencyScope` + +Collects remapped bundle dependencies. + +### `_crochet*RuntimeRemappedDependencies` +`dependencyScope` + +Collects remapped bundle dependencies. + +### `_crochet*CompileRemapped` +`consumable` extends `_crochet*CompileRemappedDependencies` + +Exposes remapped version of bundle dependencies, for local and cross-project consumption. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.bundle-:-compile-remapped:1.0.0` + +### `_crochet*RuntimeRemapped` +`consumable` extends `_crochet*RuntimeRemappedDependencies` + +Exposes remapped version of bundle dependencies, for local and cross-project consumption. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.bundle-:-runtime-remapped:1.0.0` + +### `_crochet*CompileExclude` +`consumable` + +Exposes bundle dependencies directly, to exclude from local remapping. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.bundle-:-compile-exclude:1.0.0` + +### `_crochet*RuntimeExclude` +`consumable` + +Exposes bundle dependencies directly, to exclude from local remapping. + +Attributes: +- `dev.lukebemish.crochet.local.distribution`: Installation default + +Capabilities: +- `dev.lukebemish.crochet.local.bundle-:-runtime-exclude:1.0.0` + +## NeoFormInstallation + +### `_crochet*PistonMetaDownloads` +`dependencyScope` + +Contains stub dependencies capable of locating various things from piston-meta that TaskGraphRunner needs to produce a +remapped minecraft jar. + +### `_crochet*ClientJarPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the original client jar of the target version. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.category` - `library` + +Excludes: +- `dev.lukebemish.crochet.mojang-stubs:minecraft-dependencies` (not needed but brought in by the target stub variant) + +### `_crochet*ServerJarPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the original server jar of the target version. + +Attributes: +- `net.neoforged.distribution` - `server` +- `org.gradle.category` - `library` + +Excludes: +- `dev.lukebemish.crochet.mojang-stubs:minecraft-dependencies` (not needed but brought in by the target stub variant) + +### `_crochet*ClientMappingsPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the client mappings of the target version. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.category` - `mappings` + +### `_crochet*ServerMappingsPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the client mappings of the target version. + +Attributes: +- `net.neoforged.distribution` - `server` +- `org.gradle.category` - `mappings` + +### `_crochet*VersionJsonPistonMetaDownloads` +`resolvable` extends `_crochet*PistonMetaDownloads` + +Produces the version json of the target version. + +Attributes: +- `org.gradle.category` - `versionjson` + +### `_crochet*NeoFormCompileClasspath` +`resolvable` extends `*MinecraftDependencies` + +Contains the compile resolution of the classpath used to decompile Minecraft by TaskGraphRunner. Resolves client +dependencies under the assumption that they are a superset of server, and since TaskGraphRunner is queried with `joined`. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.usage` - `java-api` + +### `_crochet*NeoFormRuntimeClasspath` +`resolvable` extends `*MinecraftDependencies` + +Contains the runtime resolution of the classpath used to decompile Minecraft by TaskGraphRunner. Resolves client +dependencies under the assumption that they are a superset of server, and since TaskGraphRunner is queried with `joined`. + +Attributes: +- `net.neoforged.distribution` - `client` +- `org.gradle.usage` - `java-runtime` + +### `*NeoForm` +`dependencyScope` + +Contains the neoform to use for this installation. + +### `_crochet*NeoFormConfigDependencies` +`resolvable` extends `*NeoForm` + +Contains dependencies added to MC by neoform. + +Attributes: +- `net.neoforged.distribution` - Installation default + +### `_crochet*NeoFormConfig` +`resolvable` extends `*NeoForm` + +Contains the neoform configuration itself. + +### `*Parchment` +`dependencyScope` + +Depends on parchment to use for this installation + +### `_crochet*ParchmentData` +`resolvable` extends `*Parchment` + +Contains the parchment data to use for this installation. + +### `_crochet*NeoForm` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RunImplementation` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +## ExternalMinecraftInstallation + +### `_crochet*AssetsProperties` +`dependencyScope` + +Depends on the generated assets properties file. + +### `_crochet*AssetsPropertiesPath` +`resolvable` extends `_crochet*AssetsProperties` + +Contains the generated assets properties file. + +## ExternalFabricInstallation + +### `_crochet*Loader` +`dependencyScope` + +Loader and dependencies, pulled from the external installation. + +### `_crochet*IntermediaryMinecraft` +`dependencyScope` + +Minecraft in intermediary, and its dependencies, pulled from the external installation. + +### `_crochet*MappingsIntermediaryNamed` +`dependencyScope` + +Depends on intermediary-to-named mappings, pulled from the external installation. + +### `_crochet*MappingsIntermediaryNamedPath` +`resolvable` extends `_crochet*MappingsIntermediaryNamed` + +Contains intermediary-to-named mappings, pulled from the external installation. + +### `_crochet*MappingsNamedIntermediary` +`dependencyScope` + +Depends on named-to-intermediary mappings, pulled from the external installation. + +### `_crochet*MappingsNamedIntermediaryPath` +`resolvable` extends `_crochet*MappingsNamedIntermediary` + +Contains named-to-intermediary mappings, pulled from the external installation. + +### `_crochet*InjectedInterfaces` +`dependencyScope` + +Depends on interface injections that should be directly expressed in the produced jar. + +### `_crochet*InjectedInterfacesPath` +`resolvable` extends `_crochet*InjectedInterfaces` + +Contains interface injections that should be directly expressed in the produced jar. + +### `_crochet*CompileRemapped` +`dependencyScope` + +Collects remapped bundle dependencies. + +### `_crochet*RuntimeRemapped` +`dependencyScope` + +Collects remapped bundle dependencies. + +### `_crochet*CompileExclude` +`dependencyScope` + +Excluded dependencies from remapping for compile. + +### `_crochet*RuntimeExclude` +`dependencyScope` + +Excluded dependencies from remapping for runtime. + +## FabricInstallationLogic + +### `mod*Implementation` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RemappingRuntimeClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*VersioningRuntimeClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `mod*CompileOnly` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*VersioningCompileClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `mod*CompileOnlyApi` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `mod*LocalRuntime` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*ExcludedRuntimeClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `mod*RuntimeOnly` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `mod*Api` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `mod*LocalImplementation` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*ModRuntimeClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RemappingCompileClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*ModCompileClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*ExcludedCompileClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*VersioningCompileDependencies` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*NonModRuntimeElements` +`consumable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*ModApiElements` +`consumable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RemappedCompileClasspath` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*ExcludedCompileDependencies` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RemappedSourcesElements` +`consumable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*ModRuntimeElements` +`consumable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*NonModApiElements` +`consumable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RunImplementation` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*ExcludedRunClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RunModClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RunVersioningClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RunRemappingClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RemappedRunClasspath` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +## Run + +### `_crochet*RunImplementation` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +### `_crochet*RunClasspath` +`resolvable` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + +## VanillaInstallationLogic + +### `_crochet*RunImplementation` +`dependencyScope` (TODO: document extendsFrom) + +TODO: document purpose, attributes, etc. + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000..e1a3f8f --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000..0faadc1 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index cea7a79..e18bc25 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index 6c03519..1777dbd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,7 +6,7 @@ pluginManagement { gradlePluginPortal() maven { - url "https://maven.lukebemish.dev/releases/" + url = "https://maven.lukebemish.dev/releases/" } } plugins { diff --git a/src/main/java/dev/lukebemish/crochet/CrochetProperties.java b/src/main/java/dev/lukebemish/crochet/CrochetProperties.java index 2533bb7..e3a8b74 100644 --- a/src/main/java/dev/lukebemish/crochet/CrochetProperties.java +++ b/src/main/java/dev/lukebemish/crochet/CrochetProperties.java @@ -14,4 +14,7 @@ private CrochetProperties() {} public static final String PISTON_META_URL = "dev.lukebemish.crochet.repositories.piston-meta-url"; public static final String PISTON_DATA_URL = "dev.lukebemish.crochet.repositories.piston-data-url"; public static final String DEPENDENCY_STUB_URL = "dev.lukebemish.crochet.repositories.dependency-stub-url"; + + public static final String CROCHET_VALIDATE_CONFIGURATIONS = "dev.lukebemish.crochet.validation.validate-configurations-path"; + public static final String CROCHET_VALIDATE_CONFIGURATIONS_MODE = "dev.lukebemish.crochet.validation.validate-configurations-mode"; } diff --git a/src/main/java/dev/lukebemish/crochet/internal/ConfigurationUtils.java b/src/main/java/dev/lukebemish/crochet/internal/ConfigurationUtils.java index 5c56624..1287652 100644 --- a/src/main/java/dev/lukebemish/crochet/internal/ConfigurationUtils.java +++ b/src/main/java/dev/lukebemish/crochet/internal/ConfigurationUtils.java @@ -1,20 +1,363 @@ package dev.lukebemish.crochet.internal; +import dev.lukebemish.crochet.CrochetProperties; import dev.lukebemish.crochet.internal.metadata.pistonmeta.PistonMetaMetadataRule; +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.artifacts.ConsumableConfiguration; +import org.gradle.api.artifacts.DependencyScopeConfiguration; +import org.gradle.api.artifacts.ResolvableConfiguration; import org.gradle.api.artifacts.component.ComponentSelector; import org.gradle.api.artifacts.result.DependencyResult; import org.gradle.api.artifacts.result.ResolvedComponentResult; import org.gradle.api.artifacts.result.ResolvedDependencyResult; import org.gradle.api.attributes.Attribute; import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.attributes.Category; +import org.gradle.api.internal.artifacts.configurations.ConfigurationRole; +import org.gradle.api.problems.ProblemSpec; +import org.gradle.api.problems.Problems; +import org.gradle.api.problems.Severity; +import org.gradle.api.provider.Property; import org.gradle.api.provider.ProviderFactory; +import org.gradle.api.services.BuildService; +import org.gradle.api.services.BuildServiceParameters; +import org.jspecify.annotations.Nullable; +import javax.inject.Inject; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; -public final class ConfigurationUtils { - private ConfigurationUtils() {} +public abstract class ConfigurationUtils implements BuildService, AutoCloseable { + @Inject + public ConfigurationUtils() {} + + enum ValidationMode { + GENERATE, + VALIDATE + } + + interface Params extends BuildServiceParameters { + Property getValidationMode(); + Property getValidationPath(); + Property getValidationEnabled(); + } + + @Inject + protected abstract Problems getProblems(); + + private final Map> classesFromConfiguration = new HashMap<>(); + private final Map> configurationRoles = new HashMap<>(); + private boolean setup = false; + + private final Map> missing = new HashMap<>(); + + @Override + public synchronized void close() throws IOException { + if (getParameters().getValidationEnabled().get() && getParameters().getValidationMode().get() == ValidationMode.GENERATE && !missing.isEmpty()) { + var prefix = new ArrayList(); + var existingLines = new LinkedHashMap>(); + var path = Path.of(getParameters().getValidationPath().get()); + var originalLines = Files.readAllLines(path); + String className = null; + var partialPrefix = new ArrayList(); + for (var line : originalLines) { + if (line.startsWith("## ")) { + if (className == null) { + prefix.addAll(partialPrefix); + } else { + existingLines.put(className, new ArrayList<>(partialPrefix)); + } + partialPrefix.clear(); + className = line.substring(3).trim(); + } + partialPrefix.add(line); + } + if (className != null) { + existingLines.put(className, new ArrayList<>(partialPrefix)); + } else { + prefix.addAll(partialPrefix); + } + for (var entry : missing.entrySet()) { + className = entry.getKey(); + var roles = entry.getValue(); + var lines = existingLines.get(className); + if (lines == null) { + lines = new ArrayList<>(); + lines.add("## "+className); + lines.add(""); + existingLines.put(className, lines); + } + for (var roleEntry : roles.entrySet()) { + var configurationName = roleEntry.getKey(); + var role = roleEntry.getValue(); + lines.add("### `"+configurationName+"`"); + lines.add("`"+role.value()+"` (TODO: document extendsFrom)"); + lines.add(""); + lines.add("TODO: document purpose, attributes, etc."); + lines.add(""); + } + } + var newLines = new ArrayList<>(prefix); + for (var entry : existingLines.entrySet()) { + newLines.addAll(entry.getValue()); + } + Files.write(Path.of(getParameters().getValidationPath().get()), newLines); + } + } + + private synchronized void setup() { + if (setup) { + return; + } + setup = true; + if (!getParameters().getValidationEnabled().get()) { + return; + } + try { + var path = Path.of(getParameters().getValidationPath().get()); + var lines = Files.readAllLines(path); + String className = null; + String configurationName = null; + boolean nextIsRole = false; + for (var line : lines) { + if (line.startsWith("## ")) { + className = line.substring(3).trim(); + nextIsRole = false; + } else if (className != null && line.startsWith("### `")) { + var firstIndex = line.indexOf('`'); + var lastIndex = line.lastIndexOf('`'); + if (firstIndex != lastIndex) { + configurationName = line.substring(firstIndex + 1, lastIndex); + classesFromConfiguration.computeIfAbsent(configurationName, k -> new HashSet<>()).add(className); + nextIsRole = true; + } else { + nextIsRole = false; + } + } else if (configurationName != null && nextIsRole) { + var firstIndex = line.indexOf('`'); + var lastIndex = line.indexOf('`', firstIndex + 1); + if (firstIndex != lastIndex && lastIndex > 0) { + var role = line.substring(firstIndex + 1, lastIndex); + configurationRoles.computeIfAbsent(configurationName, k -> new HashMap<>()).put(className, ConfigurationRole.fromValue(role)); + } + nextIsRole = false; + } + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private void generateOrThrow(Action action, String className, String configurationName, ConfigurationRole role) { + if (getParameters().getValidationMode().get() == ValidationMode.GENERATE) { + missing.computeIfAbsent(className, k -> new HashMap<>()).put(configurationName, role); + getProblems().getReporter().reporting(action); + } else { + throw getProblems().getReporter().throwing(action); + } + } + + @SuppressWarnings("UnstableApiUsage") + private void validateRoleAndCaller(String name, ConfigurationRole role) { + String callingClass = null; + String fileName = null; + int lineNumber = -1; + for (var element : Thread.currentThread().getStackTrace()) { + var className = element.getClassName(); + if (className.equals(ConfigurationUtils.class.getName()) || className.equals(Thread.class.getName())) { + continue; + } + var parts = className.split("\\."); + callingClass = parts[parts.length - 1].split("\\$")[0]; + lineNumber = element.getLineNumber(); + fileName = element.getFileName(); + break; + } + if (callingClass == null) { + throw getProblems().getReporter().throwing(problem -> + problem + .id("crochet-configuration-validation-unknown-caller", "Unknown caller for configuration name pattern", SimpleProblemGroup.CONFIGURATION_VALIDATION) + .contextualLabel("Could not determine calling class for configuration name pattern "+name) + .severity(Severity.ERROR) + .solution("Make sure ConfigurationUtils is called in such a way that the stack can be inspected") + .stackLocation() + .withException(new IllegalStateException("Could not determine calling class for configuration name pattern "+name)) + ); + } + final var finalCallingClass = callingClass; + final var finalFileName = Objects.requireNonNull(fileName); + final var finalLineNumber = lineNumber; + if (!classesFromConfiguration.getOrDefault(name, Set.of()).contains(callingClass)) { + generateOrThrow(problem -> + problem + .id("crochet-configuration-validation-undocumented-pattern", "Undocumented configuration name pattern for caller", SimpleProblemGroup.CONFIGURATION_VALIDATION) + .contextualLabel("Configuration name pattern "+name+" does not match documented patterns for creating class "+finalCallingClass) + .severity(Severity.ERROR) + .solution("Document pattern "+name+" for class "+finalCallingClass) + .lineInFileLocation(finalFileName, finalLineNumber) + .withException(new IllegalStateException("Configuration name pattern "+name+" does not match documented patterns for creating class "+finalCallingClass)), + callingClass, name, role + ); + return; + } + var roleMap = configurationRoles.get(name); + if (roleMap == null || roleMap.get(callingClass) == null) { + throw getProblems().getReporter().throwing(problem -> + problem + .id("crochet-configuration-validation-undocumented-role", "Undocumented configuration name pattern role for caller", SimpleProblemGroup.CONFIGURATION_VALIDATION) + .contextualLabel("Could not determine expected role for configuration name pattern "+name+" in creating class "+finalCallingClass) + .severity(Severity.ERROR) + .solution("Document role for pattern "+name+" for class "+finalCallingClass) + .lineInFileLocation(finalFileName, finalLineNumber) + .withException(new IllegalStateException("Could not determine expected role for configuration name pattern "+name+" in creating class "+finalCallingClass)) + ); + } + if (roleMap.get(callingClass) != role) { + throw getProblems().getReporter().throwing(problem -> + problem + .id("crochet-configuration-validation-incorrect-role", "Incorrect role for configuration name pattern", SimpleProblemGroup.CONFIGURATION_VALIDATION) + .contextualLabel("Configuration name pattern "+name+" does not match documented patterns for creating class "+finalCallingClass+"; expected role "+roleMap.get(finalCallingClass)+", got "+role) + .severity(Severity.ERROR) + .solution("Modify documented or actual role for pattern "+name+" for class "+finalCallingClass) + .lineInFileLocation(finalFileName, finalLineNumber) + .withException(new IllegalStateException("Configuration name pattern "+name+" does not match documented patterns for creating class "+finalCallingClass+"; expected role "+roleMap.get(finalCallingClass)+", got "+role)) + ); + } + } + + private void validate(@Nullable String prefix, @Nullable String suffix, ConfigurationRole role) { + setup(); + + if (!getParameters().getValidationEnabled().get()) { + return; + } + var name = NameUtils.name("*", prefix, suffix); + validateRoleAndCaller(name, role); + } + + private void validateInternal(@Nullable String suffix, ConfigurationRole role) { + setup(); + + if (!getParameters().getValidationEnabled().get()) { + return; + } + + var name = NameUtils.internal("*", suffix); + validateRoleAndCaller(name, role); + } + + private static ConfigurationUtils validator(Project project) { + return project.getGradle().getSharedServices().registerIfAbsent("crochetConfigurationNameValidator", ConfigurationUtils.class, spec -> { + var validationPath = project.getProviders() + .gradleProperty(CrochetProperties.CROCHET_VALIDATE_CONFIGURATIONS) + .orElse(project.getProviders().systemProperty(CrochetProperties.CROCHET_VALIDATE_CONFIGURATIONS)) + .map(p -> project.getIsolated().getRootProject().getProjectDirectory().file(p).getAsFile().getAbsolutePath().toString()) + .orElse(""); + spec.getParameters().getValidationPath().set(validationPath); + var validationMode = project.getProviders() + .gradleProperty(CrochetProperties.CROCHET_VALIDATE_CONFIGURATIONS_MODE) + .map(s -> ValidationMode.valueOf(s.toUpperCase(Locale.ROOT))) + .orElse(ValidationMode.VALIDATE); + spec.getParameters().getValidationMode().set(validationMode); + spec.getParameters().getValidationEnabled().set(validationPath.map(s -> !s.isEmpty())); + }).get(); + } + + private static void validateName(Project project, @Nullable String prefix, @Nullable String suffix, ConfigurationRole role) { + validator(project).validate(prefix, suffix, role); + } + + private static void validateInternalName(Project project, @Nullable String suffix, ConfigurationRole role) { + validator(project).validateInternal(suffix, role); + } + + @SuppressWarnings("UnstableApiUsage") + public static DependencyScopeConfiguration dependencyScope(Project project, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + validateName(project, prefix, suffix, ConfigurationRole.DEPENDENCY_SCOPE); + var fullName = NameUtils.name(parent, prefix, suffix); + return project.getConfigurations().dependencyScope(fullName, action).get(); + } + + @SuppressWarnings("UnstableApiUsage") + public static DependencyScopeConfiguration dependencyScope(ExtensionHolder holder, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + return dependencyScope(((ProjectHolder) holder.extension).project, parent, prefix, suffix, action); + } + + @SuppressWarnings("UnstableApiUsage") + public static ResolvableConfiguration resolvable(Project project, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + validateName(project, prefix, suffix, ConfigurationRole.RESOLVABLE); + var fullName = NameUtils.name(parent, prefix, suffix); + return project.getConfigurations().resolvable(fullName, action).get(); + } + + @SuppressWarnings("UnstableApiUsage") + public static ResolvableConfiguration resolvable(ExtensionHolder holder, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + return resolvable(((ProjectHolder) holder.extension).project, parent, prefix, suffix, action); + } + + @SuppressWarnings("UnstableApiUsage") + public static ConsumableConfiguration consumable(Project project, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + validateName(project, prefix, suffix, ConfigurationRole.CONSUMABLE); + var fullName = NameUtils.name(parent, prefix, suffix); + return project.getConfigurations().consumable(fullName, action).get(); + } + + @SuppressWarnings("UnstableApiUsage") + public static ConsumableConfiguration consumable(ExtensionHolder holder, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + return consumable(((ProjectHolder) holder.extension).project, parent, prefix, suffix, action); + } + + @SuppressWarnings("UnstableApiUsage") + public static DependencyScopeConfiguration dependencyScopeInternal(Project project, String parent, @Nullable String suffix, Action action) { + validateInternalName(project, suffix, ConfigurationRole.DEPENDENCY_SCOPE); + var fullName = NameUtils.internal(parent, suffix); + return project.getConfigurations().dependencyScope(fullName, action).get(); + } + + @SuppressWarnings("UnstableApiUsage") + public static DependencyScopeConfiguration dependencyScopeInternal(ExtensionHolder holder, String parent, @Nullable String suffix, Action action) { + return dependencyScopeInternal(((ProjectHolder) holder.extension).project, parent, suffix, action); + } + + @SuppressWarnings("UnstableApiUsage") + public static ResolvableConfiguration resolvableInternal(Project project, String parent, @Nullable String suffix, Action action) { + validateInternalName(project, suffix, ConfigurationRole.RESOLVABLE); + var fullName = NameUtils.internal(parent, suffix); + return project.getConfigurations().resolvable(fullName, action).get(); + } + + @SuppressWarnings("UnstableApiUsage") + public static ResolvableConfiguration resolvableInternal(ExtensionHolder holder, String parent, @Nullable String suffix, Action action) { + return resolvableInternal(((ProjectHolder) holder.extension).project, parent, suffix, action); + } + + @SuppressWarnings("UnstableApiUsage") + public static ConsumableConfiguration consumableInternal(Project project, String parent, @Nullable String suffix, Action action) { + validateInternalName(project, suffix, ConfigurationRole.CONSUMABLE); + var fullName = NameUtils.internal(parent, suffix); + return project.getConfigurations().consumable(fullName, action).get(); + } + + @SuppressWarnings("UnstableApiUsage") + public static ConsumableConfiguration consumableInternal(ExtensionHolder holder, String parent, @Nullable String suffix, Action action) { + return consumableInternal(((ProjectHolder) holder.extension).project, parent, suffix, action); + } @SuppressWarnings({"rawtypes", "unchecked"}) public static void copyAttributes(AttributeContainer source, AttributeContainer destination, ProviderFactory providerFactory) { @@ -59,4 +402,85 @@ public static String extractMinecraftVersion(ResolvedComponentResult component) } return candidate; } + + public enum ConfigurationRole { + DEPENDENCY_SCOPE("dependencyScope"), + RESOLVABLE("resolvable"), + CONSUMABLE("consumable"); + + private final String value; + + ConfigurationRole(String value) { + this.value = value; + } + + private static final Map BY_VALUE = Arrays.stream(values()) + .collect(Collectors.toMap(ConfigurationRole::value, e -> e)); + + public static ConfigurationRole fromValue(String value) { + var out = BY_VALUE.get(value); + if (out == null) { + throw new IllegalArgumentException("Unknown configuration role: "+value); + } + return out; + } + + public String value() { + return value; + } + } + + @SuppressWarnings("UnstableApiUsage") + public static DependencyScopeConfiguration pistonMetaDependencies(ExtensionHolder holder, String name) { + return dependencyScopeInternal(holder, name, "pistonMetaDownloads", c -> {}); + } + + public enum PistonMetaPiece { + CLIENT_JAR, + SERVER_JAR, + VERSION_JSON, + CLIENT_MAPPINGS, + SERVER_MAPPINGS; + + private String configName() { + return switch (this) { + case CLIENT_JAR -> "clientJarPistonMetaDownloads"; + case SERVER_JAR -> "serverJarPistonMetaDownloads"; + case VERSION_JSON -> "versionJsonPistonMetaDownloads"; + case CLIENT_MAPPINGS -> "clientMappingsPistonMetaDownloads"; + case SERVER_MAPPINGS -> "serverMappingsPistonMetaDownloads"; + }; + } + } + + @SuppressWarnings("UnstableApiUsage") + public static ResolvableConfiguration pistonMeta(ExtensionHolder holder, String name, DependencyScopeConfiguration minecraftPistonMeta, PistonMetaPiece piece) { + var project = ((ProjectHolder) holder.extension).project; + return resolvableInternal(holder, name, piece.configName(), c -> { + c.extendsFrom(minecraftPistonMeta); + switch (piece) { + case CLIENT_JAR, SERVER_JAR -> { + c.exclude(Map.of( + "group", CrochetRepositoriesPlugin.MOJANG_STUBS_GROUP, + "module", PistonMetaMetadataRule.MINECRAFT_DEPENDENCIES + )); + c.attributes(attributes -> { + attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, piece == PistonMetaPiece.CLIENT_JAR ? "client" : "server"); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + }); + } + case CLIENT_MAPPINGS, SERVER_MAPPINGS -> { + c.attributes(attributes -> { + attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, piece == PistonMetaPiece.CLIENT_MAPPINGS ? "client" : "server"); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, "mappings")); + }); + } + case VERSION_JSON -> { + c.attributes(attributes -> { + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, "versionjson")); + }); + } + } + }); + } } diff --git a/src/main/java/dev/lukebemish/crochet/internal/CrochetProjectPlugin.java b/src/main/java/dev/lukebemish/crochet/internal/CrochetProjectPlugin.java index aeaa6ac..f559bb9 100644 --- a/src/main/java/dev/lukebemish/crochet/internal/CrochetProjectPlugin.java +++ b/src/main/java/dev/lukebemish/crochet/internal/CrochetProjectPlugin.java @@ -15,15 +15,14 @@ import org.gradle.api.attributes.java.TargetJvmVersion; import org.gradle.api.logging.configuration.ShowStacktrace; import org.gradle.api.tasks.SourceSetContainer; -import org.jetbrains.annotations.NotNull; import javax.inject.Inject; public class CrochetProjectPlugin implements Plugin { - public static final String TASK_GRAPH_RUNNER_CONFIGURATION_NAME = "crochetTaskGraphRunnerClasspath"; - public static final String TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME = "crochetTaskGraphRunnerDependencies"; - public static final String DEV_LAUNCH_CONFIGURATION_NAME = "crochetDevLaunchClasspath"; - public static final String TERMINAL_CONSOLE_APPENDER_CONFIGURATION_NAME = "crochetTerminalConsoleAppender"; + public static final String TASK_GRAPH_RUNNER_CONFIGURATION_NAME = "_crochetTaskGraphRunnerClasspath"; + public static final String TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME = "_crochetTaskGraphRunnerToolsClasspath"; + public static final String DEV_LAUNCH_CONFIGURATION_NAME = "_crochetDevLaunch"; + public static final String TERMINAL_CONSOLE_APPENDER_CONFIGURATION_NAME = "_crochetTerminalConsoleAppender"; public static final String VERSION = CrochetProjectPlugin.class.getPackage().getImplementationVersion(); @@ -39,7 +38,7 @@ public class CrochetProjectPlugin implements Plugin { public static final String CROCHET_REMAP_TYPE_NON_REMAP = "not-to-remap"; @Override - public void apply(@NotNull Project project) { + public void apply(Project project) { if (project.getProviders().gradleProperty(CrochetProperties.ADD_LIKELY_REPOSITORIES).map(Boolean::parseBoolean).orElse(true).get()) { if (!project.getPlugins().hasPlugin(CrochetRepositoriesMarker.class)) { project.getPluginManager().apply(CrochetRepositoriesPlugin.class); @@ -55,41 +54,56 @@ public void apply(@NotNull Project project) { project.getExtensions().create("crochet.internal.mappingsConfigurationContainer", MappingsConfigurationCounter.class); // TaskGraphRunner - project.getConfigurations().register(TASK_GRAPH_RUNNER_CONFIGURATION_NAME, config -> { + var taskGraphRunnerDependencies = ConfigurationUtils.dependencyScopeInternal(project, "", "TaskGraphRunner", config -> {}); + var taskGraphRunnerClasspath = ConfigurationUtils.resolvableInternal(project, "", "TaskGraphRunnerClasspath", config -> { config.attributes(attributes -> { // TaskGraphRunner runs on 21 in general attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 21); // Prefer shadowed jar attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.class, Bundling.SHADOWED)); }); - config.setCanBeConsumed(false); + config.extendsFrom(taskGraphRunnerDependencies); }); - project.getDependencies().add(TASK_GRAPH_RUNNER_CONFIGURATION_NAME, "dev.lukebemish:taskgraphrunner:" + Versions.TASK_GRAPH_RUNNER); + if (!TASK_GRAPH_RUNNER_CONFIGURATION_NAME.equals(taskGraphRunnerClasspath.getName())) { + throw new IllegalStateException("TaskGraphRunnerClasspath configuration name is not as expected: " + taskGraphRunnerClasspath.getName() + ", expected "+TASK_GRAPH_RUNNER_CONFIGURATION_NAME); + } + project.getDependencies().add(taskGraphRunnerDependencies.getName(), "dev.lukebemish:taskgraphrunner:" + Versions.TASK_GRAPH_RUNNER); - project.getConfigurations().register(TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME, config -> { + var taskGraphRunnerToolsDependencies = ConfigurationUtils.dependencyScopeInternal(project, "", "TaskGraphRunnerTools", config -> {}); + var taskGraphRunnerToolsClasspath = ConfigurationUtils.resolvableInternal(project, "", "TaskGraphRunnerToolsClasspath", config -> { config.attributes(attributes -> { // TaskGraphRunner runs on 21 in general attributes.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 21); }); - config.setCanBeConsumed(false); + config.extendsFrom(taskGraphRunnerToolsDependencies); }); - ((ModuleDependency) project.getDependencies().add(TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME, "dev.lukebemish:taskgraphrunner:" + Versions.TASK_GRAPH_RUNNER)).capabilities(capabilities -> { + if (!TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME.equals(taskGraphRunnerToolsClasspath.getName())) { + throw new IllegalStateException("TaskGraphRunnerToolsClasspath configuration name is not as expected: " + taskGraphRunnerToolsClasspath.getName() + ", expected "+TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME); + } + + ((ModuleDependency) project.getDependencies().add(taskGraphRunnerToolsDependencies.getName(), "dev.lukebemish:taskgraphrunner:" + Versions.TASK_GRAPH_RUNNER)).capabilities(capabilities -> { capabilities.requireCapability("dev.lukebemish:taskgraphrunner-external-tools"); }); - ((ModuleDependency) project.getDependencies().add(TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME, "dev.lukebemish.crochet:tools:" + VERSION)).attributes(attributes -> { + ((ModuleDependency) project.getDependencies().add(taskGraphRunnerToolsDependencies.getName(), "dev.lukebemish.crochet:tools:" + VERSION)).attributes(attributes -> { attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.class, Bundling.SHADOWED)); }); - ((ModuleDependency) project.getDependencies().add(TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME, "dev.lukebemish:christen:" + Versions.CHRISTEN)).attributes(attributes -> { + ((ModuleDependency) project.getDependencies().add(taskGraphRunnerToolsDependencies.getName(), "dev.lukebemish:christen:" + Versions.CHRISTEN)).attributes(attributes -> { attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.class, Bundling.SHADOWED)); }); // runs - project.getConfigurations().register(DEV_LAUNCH_CONFIGURATION_NAME); - project.getDependencies().add(DEV_LAUNCH_CONFIGURATION_NAME, "net.neoforged:DevLaunch:" + Versions.DEV_LAUNCH); + var devLaunchConfiguration = ConfigurationUtils.dependencyScopeInternal(project, "", "DevLaunch", config -> {}); + if (!DEV_LAUNCH_CONFIGURATION_NAME.equals(devLaunchConfiguration.getName())) { + throw new IllegalStateException("DevLaunch configuration name is not as expected: " + devLaunchConfiguration.getName() + ", expected "+DEV_LAUNCH_CONFIGURATION_NAME); + } + project.getDependencies().add(devLaunchConfiguration.getName(), "net.neoforged:DevLaunch:" + Versions.DEV_LAUNCH); - project.getConfigurations().register(TERMINAL_CONSOLE_APPENDER_CONFIGURATION_NAME); - project.getDependencies().add(TERMINAL_CONSOLE_APPENDER_CONFIGURATION_NAME, "net.minecrell:terminalconsoleappender:" + Versions.TERMINAL_CONSOLE_APPENDER); + var terminalConsoleAppenderConfiguration = ConfigurationUtils.dependencyScopeInternal(project, "", "TerminalConsoleAppender", config -> {}); + if (!TERMINAL_CONSOLE_APPENDER_CONFIGURATION_NAME.equals(terminalConsoleAppenderConfiguration.getName())) { + throw new IllegalStateException("TerminalConsoleAppender configuration name is not as expected: " + terminalConsoleAppenderConfiguration.getName() + ", expected "+TERMINAL_CONSOLE_APPENDER_CONFIGURATION_NAME); + } + project.getDependencies().add(terminalConsoleAppenderConfiguration.getName(), "net.minecrell:terminalconsoleappender:" + Versions.TERMINAL_CONSOLE_APPENDER); // configurations setupConventionalConfigurations(project); @@ -130,10 +144,12 @@ private static void setupConventionalConfigurations(Project project) { var compileClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()); var runtimeClasspath = project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()); - var localRuntime = project.getConfigurations().maybeCreate(sourceSet.getTaskName(null, "localRuntime")); + var localRuntime = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), null, "localRuntime", c -> {}); runtimeClasspath.extendsFrom(localRuntime); - var localImplementation = project.getConfigurations().maybeCreate(sourceSet.getTaskName(null, "localImplementation")); + // TODO: make sure that local run classpaths actually pull in the source set's runtime classpath. + + var localImplementation = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), null, "localImplementation", c -> {}); compileClasspath.extendsFrom(localImplementation); runtimeClasspath.extendsFrom(localImplementation); }); diff --git a/src/main/java/dev/lukebemish/crochet/internal/ExtensionHolder.java b/src/main/java/dev/lukebemish/crochet/internal/ExtensionHolder.java new file mode 100644 index 0000000..47ec29c --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/ExtensionHolder.java @@ -0,0 +1,14 @@ +package dev.lukebemish.crochet.internal; + +import dev.lukebemish.crochet.model.CrochetExtension; + +import javax.inject.Inject; + +abstract public class ExtensionHolder { + final CrochetExtension extension; + + @Inject + public ExtensionHolder(CrochetExtension extension) { + this.extension = extension; + } +} diff --git a/src/main/java/dev/lukebemish/crochet/internal/FileListStringifier.java b/src/main/java/dev/lukebemish/crochet/internal/FileListStringifier.java index 974a141..2baac3c 100644 --- a/src/main/java/dev/lukebemish/crochet/internal/FileListStringifier.java +++ b/src/main/java/dev/lukebemish/crochet/internal/FileListStringifier.java @@ -6,7 +6,6 @@ import java.io.File; import java.util.ArrayList; import java.util.Comparator; -import java.util.List; public abstract class FileListStringifier { @InputFiles diff --git a/src/main/java/dev/lukebemish/crochet/internal/MappingsConfigurationCounter.java b/src/main/java/dev/lukebemish/crochet/internal/MappingsConfigurationCounter.java index 7f622f2..14ca3fc 100644 --- a/src/main/java/dev/lukebemish/crochet/internal/MappingsConfigurationCounter.java +++ b/src/main/java/dev/lukebemish/crochet/internal/MappingsConfigurationCounter.java @@ -1,7 +1,8 @@ package dev.lukebemish.crochet.internal; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.DependencyScopeConfiguration; +import org.gradle.api.artifacts.ResolvableConfiguration; import javax.inject.Inject; import java.util.concurrent.atomic.AtomicInteger; @@ -15,7 +16,14 @@ public abstract class MappingsConfigurationCounter { @Inject public MappingsConfigurationCounter() {} - public Configuration newConfiguration() { - return getProject().getConfigurations().create("crochetMappings" + counter.getAndIncrement()); + public Configurations newConfiguration() { + var dependencies = ConfigurationUtils.dependencyScopeInternal(getProject(), String.valueOf(counter.getAndIncrement()), "counterMappings", c -> {}); + var classpath = ConfigurationUtils.resolvableInternal(getProject(), String.valueOf(counter.getAndIncrement()), "counterMappingsClasspath", c -> { + c.extendsFrom(dependencies); + }); + return new Configurations(classpath, dependencies); } + + @SuppressWarnings("UnstableApiUsage") + public record Configurations(ResolvableConfiguration classpath, DependencyScopeConfiguration dependencies) {} } diff --git a/src/main/java/dev/lukebemish/crochet/internal/Memoize.java b/src/main/java/dev/lukebemish/crochet/internal/Memoize.java new file mode 100644 index 0000000..dcaf423 --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/Memoize.java @@ -0,0 +1,94 @@ +package dev.lukebemish.crochet.internal; + +import org.gradle.api.Action; +import org.jspecify.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; + +public sealed abstract class Memoize implements Supplier { + public abstract Memoize configure(Action action); + + public static Memoize of(Supplier supplier) { + return new Impl<>(supplier); + } + + private sealed interface Status { + T get(); + + record Uninitialized(List> actions) implements Status { + @Override + public T get() { + throw new IllegalStateException("Value not initialized"); + } + } + record Initialized(T value) implements Status { + @Override + public T get() { + return value; + } + + } + record Exception(RuntimeException exception) implements Status { + @Override + public T get() { + throw exception; + } + } + } + + private static final class Impl extends Memoize { + private final AtomicReference> value = new AtomicReference<>(new Status.Uninitialized<>(new ArrayList<>())); + private @Nullable Supplier supplier; + + Impl(Supplier supplier) { + this.supplier = supplier; + } + + @Override + public Memoize configure(Action action) { + value.updateAndGet(status -> switch (status) { + case Status.Exception v -> v; + case Status.Initialized v -> { + action.execute(v.value()); + yield v; + } + case Status.Uninitialized v -> { + v.actions().add(action); + yield v; + } + }); + return this; + } + + @Override + public T get() { + return value.updateAndGet(status -> switch (status) { + case Status.Uninitialized uninitialized -> { + try { + try { + var value = Objects.requireNonNull(supplier).get(); + uninitialized.actions.forEach(action -> action.execute(value)); + yield new Status.Initialized<>(value); + } catch (Throwable e) { + RuntimeException exception; + if (e instanceof RuntimeException runtimeException) { + exception = runtimeException; + } else { + exception = new RuntimeException(e); + } + yield new Status.Exception<>(exception); + } + } finally { + supplier = null; + } + } + case Status.Initialized initialized -> initialized; + case Status.Exception exception -> exception; + }).get(); + } + } +} diff --git a/src/main/java/dev/lukebemish/crochet/internal/NameProvider.java b/src/main/java/dev/lukebemish/crochet/internal/NameProvider.java new file mode 100644 index 0000000..5f26c43 --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/NameProvider.java @@ -0,0 +1,21 @@ +package dev.lukebemish.crochet.internal; + +import org.jspecify.annotations.Nullable; + +public sealed interface NameProvider { + String name(String parent); + + record Named(@Nullable String prefix, @Nullable String suffix) implements NameProvider { + @Override + public String name(String parent) { + return NameUtils.name(parent, prefix, suffix); + } + } + + record Internal(String name) implements NameProvider { + @Override + public String name(String parent) { + return NameUtils.internal(parent, name); + } + } +} diff --git a/src/main/java/dev/lukebemish/crochet/internal/NameUtils.java b/src/main/java/dev/lukebemish/crochet/internal/NameUtils.java new file mode 100644 index 0000000..758a639 --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/NameUtils.java @@ -0,0 +1,20 @@ +package dev.lukebemish.crochet.internal; + +import org.apache.commons.lang3.StringUtils; +import org.jspecify.annotations.Nullable; + +public final class NameUtils { + private NameUtils() {} + + public static String name(String parent, @Nullable String prefix, @Nullable String suffix) { + return StringUtils.uncapitalize( + (prefix != null ? StringUtils.capitalize(prefix) : "") + + (!"main".equals(parent) ? StringUtils.capitalize(parent) : "") + + (suffix != null ? StringUtils.capitalize(suffix) : "") + ); + } + + public static String internal(String parent, @Nullable String suffix) { + return "_crochet"+ StringUtils.capitalize(parent) + (suffix != null ? StringUtils.capitalize(suffix) : ""); + } +} diff --git a/src/main/java/dev/lukebemish/crochet/internal/ProjectHolder.java b/src/main/java/dev/lukebemish/crochet/internal/ProjectHolder.java new file mode 100644 index 0000000..5fa57d0 --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/ProjectHolder.java @@ -0,0 +1,14 @@ +package dev.lukebemish.crochet.internal; + +import org.gradle.api.Project; + +import javax.inject.Inject; + +public abstract class ProjectHolder { + final Project project; + + @Inject + public ProjectHolder(Project project) { + this.project = project; + } +} diff --git a/src/main/java/dev/lukebemish/crochet/internal/SimpleProblemGroup.java b/src/main/java/dev/lukebemish/crochet/internal/SimpleProblemGroup.java new file mode 100644 index 0000000..5bf4332 --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/SimpleProblemGroup.java @@ -0,0 +1,34 @@ +package dev.lukebemish.crochet.internal; + +import org.gradle.api.problems.ProblemGroup; +import org.jspecify.annotations.Nullable; + +@SuppressWarnings("UnstableApiUsage") +public class SimpleProblemGroup implements ProblemGroup { + public static final ProblemGroup CONFIGURATION_VALIDATION = new SimpleProblemGroup("crochet-configuration-validation", "Crochet Configuration Validation", null); + + private final String name; + private final String displayName; + private final @Nullable ProblemGroup parent; + + public SimpleProblemGroup(String name, String displayName, @Nullable ProblemGroup parent) { + this.name = name; + this.displayName = displayName; + this.parent = parent; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public @Nullable ProblemGroup getParent() { + return parent; + } +} diff --git a/src/main/java/dev/lukebemish/crochet/internal/TaskUtils.java b/src/main/java/dev/lukebemish/crochet/internal/TaskUtils.java new file mode 100644 index 0000000..d4a4cab --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/TaskUtils.java @@ -0,0 +1,41 @@ +package dev.lukebemish.crochet.internal; + +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.TaskProvider; +import org.jspecify.annotations.Nullable; + +public final class TaskUtils { + private TaskUtils() {} + + public static TaskProvider register(TaskContainer container, Class type, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + var fullName = NameUtils.name(parent, prefix, suffix); + return container.register(fullName, type, action); + } + + public static TaskProvider register(Project project, Class type, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + return register(project.getTasks(), type, parent, prefix, suffix, action); + } + + public static TaskProvider register(ExtensionHolder holder, Class type, String parent, @Nullable String prefix, @Nullable String suffix, Action action) { + return register(((ProjectHolder) holder.extension).project, type, parent, prefix, suffix, action); + } + + public static TaskProvider registerInternal(TaskContainer container, Class type, String parent, @Nullable String suffix, Action action) { + var fullName = NameUtils.internal(parent, suffix); + return container.register(fullName, type, t -> { + action.execute(t); + t.setGroup("crochet setup"); + }); + } + + public static TaskProvider registerInternal(Project project, Class type, String parent, @Nullable String suffix, Action action) { + return registerInternal(project.getTasks(), type, parent, suffix, action); + } + + public static TaskProvider registerInternal(ExtensionHolder holder, Class type, String parent, @Nullable String suffix, Action action) { + return registerInternal(((ProjectHolder) holder.extension).project, type, parent, suffix, action); + } +} diff --git a/src/main/java/dev/lukebemish/crochet/internal/metadata/package-info.java b/src/main/java/dev/lukebemish/crochet/internal/metadata/package-info.java new file mode 100644 index 0000000..c118529 --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/metadata/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@ApiStatus.Internal +package dev.lukebemish.crochet.internal.metadata; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/dev/lukebemish/crochet/internal/package-info.java b/src/main/java/dev/lukebemish/crochet/internal/package-info.java index 9c7dd42..d8e0e7c 100644 --- a/src/main/java/dev/lukebemish/crochet/internal/package-info.java +++ b/src/main/java/dev/lukebemish/crochet/internal/package-info.java @@ -1,4 +1,6 @@ @ApiStatus.Internal +@NullMarked package dev.lukebemish.crochet.internal; import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/src/main/java/dev/lukebemish/crochet/internal/tasks/MakeRunDirectories.java b/src/main/java/dev/lukebemish/crochet/internal/tasks/MakeRunDirectories.java new file mode 100644 index 0000000..5e9a760 --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/internal/tasks/MakeRunDirectories.java @@ -0,0 +1,32 @@ +package dev.lukebemish.crochet.internal.tasks; + +import org.gradle.api.DefaultTask; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.TaskAction; + +import javax.inject.Inject; + +public abstract class MakeRunDirectories extends DefaultTask { + @Inject + public MakeRunDirectories() { + this.getOutputs().upToDateWhen(t -> { + for (var dir : ((MakeRunDirectories) t).getRunDirectories()) { + if (!dir.exists()) { + return false; + } + } + return true; + }); + } + + @Internal + public abstract ConfigurableFileCollection getRunDirectories(); + + @TaskAction + public void makeRunDirectories() { + for (var dir : getRunDirectories()) { + dir.mkdirs(); + } + } +} diff --git a/src/main/java/dev/lukebemish/crochet/model/AbstractExternalVanillaInstallation.java b/src/main/java/dev/lukebemish/crochet/model/AbstractExternalVanillaInstallation.java deleted file mode 100644 index 6f673cf..0000000 --- a/src/main/java/dev/lukebemish/crochet/model/AbstractExternalVanillaInstallation.java +++ /dev/null @@ -1,10 +0,0 @@ -package dev.lukebemish.crochet.model; - -import javax.inject.Inject; - -public abstract class AbstractExternalVanillaInstallation extends ExternalMinecraftInstallation { - @Inject - public AbstractExternalVanillaInstallation(String name, CrochetExtension extension) { - super(name, extension); - } -} diff --git a/src/main/java/dev/lukebemish/crochet/model/AbstractVanillaInstallation.java b/src/main/java/dev/lukebemish/crochet/model/AbstractVanillaInstallation.java index ad7128c..d25b5f2 100644 --- a/src/main/java/dev/lukebemish/crochet/model/AbstractVanillaInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/AbstractVanillaInstallation.java @@ -1,6 +1,7 @@ package dev.lukebemish.crochet.model; import dev.lukebemish.crochet.CrochetProperties; +import dev.lukebemish.crochet.internal.ConfigurationUtils; import dev.lukebemish.crochet.internal.CrochetProjectPlugin; import dev.lukebemish.crochet.internal.CrochetRepositoriesPlugin; import dev.lukebemish.crochet.internal.metadata.pistonmeta.PistonMetaMetadataRule; @@ -11,10 +12,8 @@ import dev.lukebemish.crochet.model.mappings.MojangOfficialMappingsStructure; import dev.lukebemish.crochet.model.mappings.ReversedMappingsStructure; import dev.lukebemish.crochet.internal.tasks.VanillaInstallationArtifacts; -import org.apache.commons.lang3.StringUtils; import org.gradle.api.Project; import org.gradle.api.artifacts.VersionConstraint; -import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; @@ -22,7 +21,6 @@ import org.jetbrains.annotations.ApiStatus; import javax.inject.Inject; -import java.util.Map; public abstract class AbstractVanillaInstallation extends LocalMinecraftInstallation { final Project project; @@ -34,42 +32,11 @@ public AbstractVanillaInstallation(String name, CrochetExtension extension) { this.project = extension.project; - var minecraftPistonMeta = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"PistonMetaDownloads"); - var clientJarPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"ClientJarPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.exclude(Map.of( - "group", CrochetRepositoriesPlugin.MOJANG_STUBS_GROUP, - "module", PistonMetaMetadataRule.MINECRAFT_DEPENDENCIES - )); - c.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "client"); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - }); - }); - var serverJarPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"ServerJarPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.exclude(Map.of( - "group", CrochetRepositoriesPlugin.MOJANG_STUBS_GROUP, - "module", PistonMetaMetadataRule.MINECRAFT_DEPENDENCIES - )); - c.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "server"); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - }); - }); - var mappingsPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"MappingsPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "client"); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, "mappings")); - }); - }); - var versionJsonPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"VersionJsonPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.attributes(attributes -> { - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, "versionjson")); - }); - }); + var minecraftPistonMeta = ConfigurationUtils.pistonMetaDependencies(this, name); + var clientJarPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.CLIENT_JAR); + var serverJarPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.SERVER_JAR); + var mappingsPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.CLIENT_MAPPINGS); + var versionJsonPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.VERSION_JSON); project.getDependencies().addProvider(minecraftPistonMeta.getName(), getMinecraft().map(v -> CrochetRepositoriesPlugin.MOJANG_STUBS_GROUP+":"+PistonMetaMetadataRule.MINECRAFT+":"+v)); this.vanillaConfigMaker = project.getObjects().newInstance(VanillaInstallationArtifacts.class); @@ -80,18 +47,16 @@ public AbstractVanillaInstallation(String name, CrochetExtension extension) { vanillaConfigMaker.getDistribution().set(getDistribution()); this.binaryArtifactsTask.configure(t -> t.getConfigMaker().set(vanillaConfigMaker)); - var decompileCompileClasspath = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"RunnerCompileClasspath", config -> { - config.extendsFrom(minecraftDependencies); - config.setCanBeConsumed(false); - config.attributes(attributes -> { + var decompileCompileClasspath = ConfigurationUtils.resolvableInternal(this, name, "RunnerCompileClasspath", c -> { + c.extendsFrom(minecraftDependencies); + c.attributes(attributes -> { attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "client"); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_API)); }); }); - var decompileRuntimeClasspath = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"RunnerRuntimeClasspath", config -> { - config.extendsFrom(minecraftDependencies); - config.setCanBeConsumed(false); - config.attributes(attributes -> { + var decompileRuntimeClasspath = ConfigurationUtils.resolvableInternal(this, name, "RunnerRuntimeClasspath", c -> { + c.extendsFrom(minecraftDependencies); + c.attributes(attributes -> { attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "client"); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); }); @@ -105,11 +70,11 @@ public AbstractVanillaInstallation(String name, CrochetExtension extension) { this.binaryArtifactsTask.configure(task -> { task.artifactsConfiguration(decompileCompileClasspath); task.artifactsConfiguration(decompileRuntimeClasspath); - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-version-json", versionJsonPistonMeta.get()); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-version-json", versionJsonPistonMeta); // Both for now as the config is always JOINED - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-client-jar", clientJarPistonMeta.get()); - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-server-jar", serverJarPistonMeta.get()); - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-mappings", mappingsPistonMeta.get(), vanillaConfigMaker.getMappings().map(AbstractVanillaInstallation::requiresVanillaMappings)); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-client-jar", clientJarPistonMeta); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-server-jar", serverJarPistonMeta); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-mappings", mappingsPistonMeta, vanillaConfigMaker.getMappings().map(AbstractVanillaInstallation::requiresVanillaMappings)); task.artifactsConfiguration(project.getConfigurations().getByName(CrochetProjectPlugin.TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME)); }); } diff --git a/src/main/java/dev/lukebemish/crochet/model/CrochetExtension.java b/src/main/java/dev/lukebemish/crochet/model/CrochetExtension.java index 779e3bb..e53c86c 100644 --- a/src/main/java/dev/lukebemish/crochet/model/CrochetExtension.java +++ b/src/main/java/dev/lukebemish/crochet/model/CrochetExtension.java @@ -1,5 +1,9 @@ package dev.lukebemish.crochet.model; +import dev.lukebemish.crochet.internal.ProjectHolder; +import dev.lukebemish.crochet.internal.TaskUtils; +import dev.lukebemish.crochet.internal.tasks.MakeRunDirectories; +import kotlin.jvm.JvmName; import org.gradle.api.Action; import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer; import org.gradle.api.NamedDomainObjectContainer; @@ -16,7 +20,7 @@ import java.util.List; import java.util.Map; -public abstract class CrochetExtension implements ExtensionAware { +public abstract class CrochetExtension extends ProjectHolder implements ExtensionAware { final TaskProvider idePostSync; final TaskProvider generateSources; final Project project; @@ -26,9 +30,11 @@ public abstract class CrochetExtension implements ExtensionAware { private final NamedDomainObjectContainer splitSourceSets; private final NamedDomainObjectContainer fabricDependencyBundles; private final NamedDomainObjectContainer sharedInstallations; + private final NamedDomainObjectContainer runs; @Inject public CrochetExtension(Project project) { + super(project); this.project = project; this.generateSources = project.getTasks().register("crochetGenerateSources", t -> t.setGroup("crochet setup")); this.idePostSync = project.getTasks().register("crochetIdeSetup", t -> { @@ -59,6 +65,10 @@ public CrochetExtension(Project project) { ExternalVanillaInstallation.class, name -> objects.newInstance(ExternalVanillaInstallation.class, name, this) ); + this.installations.registerFactory( + ExternalFabricInstallation.class, + name -> objects.newInstance(ExternalFabricInstallation.class, name, this) + ); // This collection should be non-lazy as it configures other lazy things (namely, tasks) this.installations.whenObjectAdded(o -> {}); @@ -72,25 +82,38 @@ public CrochetExtension(Project project) { startParameter.setTaskRequests(taskRequests); } - // Runs should also be non-lazy, to trigger task creation - this.getRuns().whenObjectAdded(o -> {}); - this.splitSourceSets = objects.domainObjectContainer(SplitSourceSet.class, name -> { - throw new UnsupportedOperationException("Cannot instantiate SplitSourceSet on this container."); - }); this.fabricDependencyBundles = objects.domainObjectContainer(FabricDependencyBundle.class, name -> { throw new UnsupportedOperationException("Cannot instantiate FabricDependencyBundle on this container."); }); + + this.splitSourceSets = objects.domainObjectContainer(SplitSourceSet.class, name -> { + throw new UnsupportedOperationException("Cannot instantiate SplitSourceSet on this container."); + }); this.sharedInstallations = objects.domainObjectContainer(SharedInstallation.class, name -> { throw new UnsupportedOperationException("Cannot instantiate SharedInstallation on this container."); }); + this.runs = objects.domainObjectContainer(Run.class); this.getExtensions().add("installations", this.installations); this.getExtensions().add("splitSourceSets", this.splitSourceSets); - this.getExtensions().add("fabricDependencyBundles", this.fabricDependencyBundles); this.getExtensions().add("sharedInstallations", this.sharedInstallations); + this.getExtensions().add("runs", this.runs); + + this.getExtensions().add("features", getFeatures()); + this.getExtensions().add("tasks", getTasks()); + + + var makeRunDirectories = TaskUtils.registerInternal(project, MakeRunDirectories.class, "", "makeRunDirectories", t -> {}); + this.idePostSync.configure(t -> t.dependsOn(makeRunDirectories)); + + // Runs should also be non-lazy, to trigger task creation + this.runs.whenObjectAdded(run -> { + makeRunDirectories.configure(t -> t.getRunDirectories().from(run.getRunDirectory())); + }); } + @JvmName(name = "getFeatures") public CrochetFeaturesContext getFeatures() { return features; } @@ -99,6 +122,7 @@ public void features(Action action) { action.execute(getFeatures()); } + @JvmName(name = "getTasks") public CrochetTasksContext getTasks() { return tasks; } @@ -119,6 +143,11 @@ public void installations(Action getInstallations() { + return installations; + } + public NamedDomainObjectProvider fabricInstallation(String name, Action action) { return installations.register(name, FabricInstallation.class, action); } @@ -135,10 +164,32 @@ public NamedDomainObjectProvider externalVanillaIns return installations.register(name, ExternalVanillaInstallation.class, action); } - public abstract NamedDomainObjectContainer getRuns(); + public NamedDomainObjectProvider externalFabricInstallation(String name, Action action) { + return installations.register(name, ExternalFabricInstallation.class, action); + } public void runs(Action> action) { - action.execute(getRuns()); + action.execute(runs); + } + + @JvmName(name = "getRuns") + public NamedDomainObjectContainer getRuns() { + return runs; + } + + @JvmName(name = "getSplitSourceSets") + public NamedDomainObjectContainer getSplitSourceSets() { + return splitSourceSets; + } + + @JvmName(name = "getFabricDependencyBundles") + public NamedDomainObjectContainer getFabricDependencyBundles() { + return fabricDependencyBundles; + } + + @JvmName(name = "getSharedInstallations") + public NamedDomainObjectContainer getSharedInstallations() { + return sharedInstallations; } private final Map sourceSets = new HashMap<>(); @@ -161,4 +212,12 @@ void forSourceSet(String installation, SourceSet sourceSet) { void addSharedInstallation(String name) { sharedInstallations.add(project.getObjects().newInstance(SharedInstallation.class, name)); } + + void addBundle(FabricDependencyBundle bundle) { + fabricDependencyBundles.add(bundle); + } + + FabricDependencyBundle getBundle(String name) { + return fabricDependencyBundles.getByName(name+"Shared"); + } } diff --git a/src/main/java/dev/lukebemish/crochet/model/CrochetSettingsExtension.java b/src/main/java/dev/lukebemish/crochet/model/CrochetSettingsExtension.java index 5f68df9..6e97b2e 100644 --- a/src/main/java/dev/lukebemish/crochet/model/CrochetSettingsExtension.java +++ b/src/main/java/dev/lukebemish/crochet/model/CrochetSettingsExtension.java @@ -4,10 +4,11 @@ import org.gradle.api.ExtensiblePolymorphicDomainObjectContainer; import org.gradle.api.initialization.Settings; import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.ExtensionAware; import javax.inject.Inject; -public abstract class CrochetSettingsExtension { +public abstract class CrochetSettingsExtension implements ExtensionAware { private final ExtensiblePolymorphicDomainObjectContainer> installations; @Inject @@ -31,6 +32,8 @@ public CrochetSettingsExtension(Settings settings) { SettingsMinecraftInstallation.NeoForm.class, name -> getObjects().newInstance(SettingsMinecraftInstallation.NeoForm.class, name, name, settings) ); + + this.getExtensions().add("installations", this.installations); } public ExtensiblePolymorphicDomainObjectContainer> getInstallations() { diff --git a/src/main/java/dev/lukebemish/crochet/model/ExternalAbstractVanillaInstallation.java b/src/main/java/dev/lukebemish/crochet/model/ExternalAbstractVanillaInstallation.java index 1dffa89..0da0a7d 100644 --- a/src/main/java/dev/lukebemish/crochet/model/ExternalAbstractVanillaInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/ExternalAbstractVanillaInstallation.java @@ -1,23 +1,10 @@ package dev.lukebemish.crochet.model; -import org.gradle.api.tasks.SourceSet; - import javax.inject.Inject; public abstract class ExternalAbstractVanillaInstallation extends ExternalMinecraftInstallation { - @Inject public ExternalAbstractVanillaInstallation(String name, CrochetExtension extension) { super(name, extension); } - - @Override - public void forFeature(SourceSet sourceSet) { - super.forFeature(sourceSet); - } - - @Override - public void forLocalFeature(SourceSet sourceSet) { - super.forLocalFeature(sourceSet); - } } diff --git a/src/main/java/dev/lukebemish/crochet/model/ExternalFabricInstallation.java b/src/main/java/dev/lukebemish/crochet/model/ExternalFabricInstallation.java new file mode 100644 index 0000000..bf7aa72 --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/model/ExternalFabricInstallation.java @@ -0,0 +1,239 @@ +package dev.lukebemish.crochet.model; + +import dev.lukebemish.crochet.internal.ConfigurationUtils; +import dev.lukebemish.crochet.internal.CrochetProjectPlugin; +import dev.lukebemish.crochet.internal.tasks.TaskGraphExecution; +import org.gradle.api.Action; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.DependencyScopeConfiguration; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ResolvableConfiguration; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.Directory; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFile; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public abstract class ExternalFabricInstallation extends ExternalAbstractVanillaInstallation { + private final FabricInstallationLogic logic; + + private final DependencyScopeConfiguration loader; + private final DependencyScopeConfiguration intermediaryMinecraft; + private final DependencyScopeConfiguration mappingsIntermediaryNamed; + private final ResolvableConfiguration mappingsIntermediaryNamedPath; + private final DependencyScopeConfiguration mappingsNamedIntermediary; + private final ResolvableConfiguration mappingsNamedIntermediaryPath; + private final DependencyScopeConfiguration injectedInterfaces; + private final ResolvableConfiguration injectedInterfacesPath; + + private final DependencyScopeConfiguration compileRemapped; + private final DependencyScopeConfiguration compileExclude; + private final DependencyScopeConfiguration runtimeRemapped; + private final DependencyScopeConfiguration runtimeExclude; + + @Inject + public ExternalFabricInstallation(String name, CrochetExtension extension) { + super(name, extension); + + this.loader = ConfigurationUtils.dependencyScopeInternal(this, name, "loader", c -> {}); + this.intermediaryMinecraft = ConfigurationUtils.dependencyScopeInternal(this, name, "intermediaryMinecraft", c -> {}); + this.mappingsIntermediaryNamed = ConfigurationUtils.dependencyScopeInternal(this, name, "mappingsIntermediaryNamed", c -> {}); + this.mappingsIntermediaryNamedPath = ConfigurationUtils.resolvableInternal(this, name, "mappingsIntermediaryNamedPath", c -> { + c.extendsFrom(this.mappingsIntermediaryNamed); + }); + this.mappingsNamedIntermediary = ConfigurationUtils.dependencyScopeInternal(this, name, "mappingsNamedIntermediary", c -> {}); + this.mappingsNamedIntermediaryPath = ConfigurationUtils.resolvableInternal(this, name, "mappingsNamedIntermediaryPath", c -> { + c.extendsFrom(this.mappingsNamedIntermediary); + }); + this.injectedInterfaces = ConfigurationUtils.dependencyScopeInternal(this, name, "injectedInterfaces", c -> {}); + this.injectedInterfacesPath = ConfigurationUtils.resolvableInternal(this, name, "injectedInterfacesPath", c -> { + c.extendsFrom(this.injectedInterfaces); + }); + + this.compileRemapped = ConfigurationUtils.dependencyScopeInternal(this, name, "compileRemapped", c -> {}); + this.compileExclude = ConfigurationUtils.dependencyScopeInternal(this, name, "compileExclude", c -> {}); + this.runtimeRemapped = ConfigurationUtils.dependencyScopeInternal(this, name, "runtimeRemapped", c -> {}); + this.runtimeExclude = ConfigurationUtils.dependencyScopeInternal(this, name, "runtimeExclude", c -> {}); + + this.nonUpgradableClientCompileVersioning.extendsFrom(compileExclude); + this.nonUpgradableClientRuntimeVersioning.extendsFrom(runtimeExclude); + + this.nonUpgradableServerCompileVersioning.extendsFrom(compileExclude); + this.nonUpgradableServerRuntimeVersioning.extendsFrom(runtimeExclude); + + var workingDirectory = extension.project.getLayout().getBuildDirectory().dir("crochet/installations/" + name); + + this.logic = new FabricInstallationLogic(this, extension.project) { + @Override + protected DependencyScopeConfiguration loaderConfiguration() { + return loader; + } + + @Override + protected DependencyScopeConfiguration intermediaryMinecraft() { + return intermediaryMinecraft; + } + + @Override + protected void extractFabricForDependencies(ResolvableConfiguration modCompileClasspath, ResolvableConfiguration modRuntimeClasspath) { + // We do not pull AWs or IIs from these dependencies + } + + @Override + protected Provider namedToIntermediary() { + var files = extension.project.files(mappingsNamedIntermediaryPath); + return extension.project.getLayout().file(extension.project.provider(files::getSingleFile)); + } + + @Override + protected Provider intermediaryToNamed() { + var files = extension.project.files(mappingsIntermediaryNamedPath); + return extension.project.getLayout().file(extension.project.provider(files::getSingleFile)); + } + + @Override + protected FileCollection namedToIntermediaryFlat() { + return extension.project.files(mappingsNamedIntermediaryPath); + } + + @Override + protected FileCollection intermediaryToNamedFlat() { + return extension.project.files(mappingsIntermediaryNamedPath); + } + + @Override + protected Configuration compileExclude() { + return compileExclude; + } + + @Override + protected Configuration runtimeExclude() { + return runtimeExclude; + } + + @Override + protected Configuration compileRemapped() { + return compileRemapped; + } + + @Override + protected Configuration runtimeRemapped() { + return runtimeRemapped; + } + + @Override + protected void includeInterfaceInjections(ConfigurableFileCollection interfaceInjectionFiles) { + interfaceInjectionFiles.from(injectedInterfacesPath); + } + + @Override + protected Provider workingDirectory() { + return workingDirectory; + } + + @Override + protected void addArtifacts(TaskGraphExecution task) { + task.artifactsConfiguration(extension.project.getConfigurations().getByName(CrochetProjectPlugin.TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME)); + } + }; + } + + public void consume(SharedInstallation sharedInstallation) { + super.consume(sharedInstallation); + this.useBundle(crochetExtension.getBundle(sharedInstallation.getName())); + } + + void useBundle(FabricDependencyBundle bundle) { + this.bundleActions.add(bundle.action); + + for (var entry : Map.of( + "compile-remapped", compileRemapped, + "compile-exclude", compileExclude, + "runtime-remapped", runtimeRemapped, + "runtime-exclude", runtimeExclude + ).entrySet()) { + var group = CROSS_PROJECT_BUNDLE_CAPABILITY_GROUP + sharingInstallationTypeTag(); + var dep = (ModuleDependency) crochetExtension.project.getDependencies().project(Map.of("path", ":")); + dep.capabilities(caps -> { + caps.requireCapability(group + ":" + bundle.getName() + "-" + entry.getKey()); + }); + dep.attributes(attribute -> { + attribute.attributeProvider(CrochetProjectPlugin.LOCAL_DISTRIBUTION_ATTRIBUTE, getDistribution().map(it -> it.name().toLowerCase(Locale.ROOT))); + }); + dep.endorseStrictVersions(); + crochetExtension.project.getDependencies().add( + entry.getValue().getName(), + dep + ); + } + } + + private final List> bundleActions = new ArrayList<>(); + + @Override + protected Map getConfigurationsToLink() { + var map = new LinkedHashMap<>(super.getConfigurationsToLink()); + map.put("loader", loader); + map.put("intermediary", intermediaryMinecraft); + map.put("mappings-intermediary-named", mappingsIntermediaryNamed); + map.put("mappings-named-intermediary", mappingsNamedIntermediary); + map.put("injected-interfaces", injectedInterfaces); + return map; + } + + @Override + protected String sharingInstallationTypeTag() { + return "fabric"; + } + + @Override + public void forFeature(SourceSet sourceSet) { + this.forFeature(sourceSet, deps -> {}); + } + + @Override + public void forLocalFeature(SourceSet sourceSet) { + this.forLocalFeature(sourceSet, deps -> {}); + } + + public void forFeature(SourceSet sourceSet, Action action) { + super.forFeature(sourceSet); + forFeatureShared(sourceSet, deps -> { + for (var bundleAction : bundleActions) { + bundleAction.execute(deps); + } + action.execute(deps); + }, false); + } + + public void forLocalFeature(SourceSet sourceSet, Action action) { + super.forLocalFeature(sourceSet); + forFeatureShared(sourceSet, deps -> { + for (var bundleAction : bundleActions) { + bundleAction.execute(deps); + } + action.execute(deps); + }, true); + } + + private void forFeatureShared(SourceSet sourceSet, Action action, boolean local) { + var dependencies = crochetExtension.project.getObjects().newInstance(FabricSourceSetDependencies.class); + action.execute(dependencies); + + logic.forFeatureShared(sourceSet, dependencies, local); + } + + @Override + void forRun(Run run, RunType runType) { + super.forRun(run, runType); + // TODO: implement + } +} diff --git a/src/main/java/dev/lukebemish/crochet/model/ExternalMinecraftInstallation.java b/src/main/java/dev/lukebemish/crochet/model/ExternalMinecraftInstallation.java index f3f0e83..ff675b9 100644 --- a/src/main/java/dev/lukebemish/crochet/model/ExternalMinecraftInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/ExternalMinecraftInstallation.java @@ -1,12 +1,14 @@ package dev.lukebemish.crochet.model; +import dev.lukebemish.crochet.internal.ConfigurationUtils; import dev.lukebemish.crochet.internal.CrochetProjectPlugin; -import org.apache.commons.lang3.StringUtils; import org.gradle.api.Action; import org.gradle.api.NamedDomainObjectProvider; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.Dependency; +import org.gradle.api.artifacts.DependencyScopeConfiguration; import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ResolvableConfiguration; import org.gradle.api.provider.Provider; import javax.inject.Inject; @@ -16,16 +18,18 @@ import java.util.function.Supplier; public abstract class ExternalMinecraftInstallation extends MinecraftInstallation { - final Configuration assetsProperties; + final DependencyScopeConfiguration assetsProperties; + final ResolvableConfiguration assetsPropertiesPath; @Inject public ExternalMinecraftInstallation(String name, CrochetExtension extension) { super(name, extension); - var project = extension.project; - - assetsProperties = project.getConfigurations().maybeCreate("crochet"+ StringUtils.capitalize(name)+"AssetsProperties"); - assetsPropertiesFiles.from(assetsProperties); + assetsProperties = ConfigurationUtils.dependencyScopeInternal(this, name, "assetsProperties", c -> {}); + assetsPropertiesPath = ConfigurationUtils.resolvableInternal(this, name, "assetsPropertiesPath", c -> { + c.extendsFrom(assetsProperties); + }); + this.assetsPropertiesFiles.from(assetsPropertiesPath); } boolean linked = false; @@ -44,16 +48,12 @@ protected Map getConfigurationsToLink() { } @SuppressWarnings("UnstableApiUsage") - public void consume(String name) { - consume(crochetExtension.project.getIsolated().getRootProject().getPath(), name); - } - public void consume(SharedInstallation sharedInstallation) { - consume(sharedInstallation.getName()); + consume(crochetExtension.project.getIsolated().getRootProject().getPath(), sharedInstallation.getName()+"Shared"); } public void consume(NamedDomainObjectProvider sharedInstallation) { - consume(sharedInstallation.getName()); + consume(sharedInstallation.get()); } public void consume(String project, String name) { diff --git a/src/main/java/dev/lukebemish/crochet/model/ExternalVanillaInstallation.java b/src/main/java/dev/lukebemish/crochet/model/ExternalVanillaInstallation.java index ba1cc78..f3b3acc 100644 --- a/src/main/java/dev/lukebemish/crochet/model/ExternalVanillaInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/ExternalVanillaInstallation.java @@ -2,19 +2,19 @@ import javax.inject.Inject; -public abstract class ExternalVanillaInstallation extends AbstractExternalVanillaInstallation { - private final VanillaInstallationRunLogic runLogic; +public abstract class ExternalVanillaInstallation extends ExternalAbstractVanillaInstallation { + private final VanillaInstallationLogic logic; @Inject public ExternalVanillaInstallation(String name, CrochetExtension extension) { super(name, extension); - runLogic = new VanillaInstallationRunLogic(this) {}; + logic = new VanillaInstallationLogic(this) {}; } @Override void forRun(Run run, RunType runType) { super.forRun(run, runType); - runLogic.forRun(run, runType); + logic.forRun(run, runType); } @Override diff --git a/src/main/java/dev/lukebemish/crochet/model/FabricDependencyBundle.java b/src/main/java/dev/lukebemish/crochet/model/FabricDependencyBundle.java index e728f50..c278e27 100644 --- a/src/main/java/dev/lukebemish/crochet/model/FabricDependencyBundle.java +++ b/src/main/java/dev/lukebemish/crochet/model/FabricDependencyBundle.java @@ -6,7 +6,7 @@ import javax.inject.Inject; public abstract class FabricDependencyBundle implements Named { - private final Action action; + final Action action; private final String name; @Inject diff --git a/src/main/java/dev/lukebemish/crochet/model/FabricInstallation.java b/src/main/java/dev/lukebemish/crochet/model/FabricInstallation.java index 275d0ba..37ecd04 100644 --- a/src/main/java/dev/lukebemish/crochet/model/FabricInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/FabricInstallation.java @@ -1,17 +1,15 @@ package dev.lukebemish.crochet.model; +import com.google.common.base.Suppliers; +import dev.lukebemish.crochet.internal.ConfigurationUtils; import dev.lukebemish.crochet.internal.CrochetProjectPlugin; import dev.lukebemish.crochet.internal.FeatureUtils; -import dev.lukebemish.crochet.internal.IdeaModelHandlerPlugin; -import dev.lukebemish.crochet.internal.InheritanceMarker; import dev.lukebemish.crochet.internal.Log4jSetup; -import dev.lukebemish.crochet.internal.tasks.ArtifactTarget; +import dev.lukebemish.crochet.internal.TaskUtils; import dev.lukebemish.crochet.internal.tasks.ExtractFabricDependencies; import dev.lukebemish.crochet.internal.tasks.FabricInstallationArtifacts; import dev.lukebemish.crochet.internal.tasks.MakeRemapClasspathFile; import dev.lukebemish.crochet.internal.tasks.MappingsWriter; -import dev.lukebemish.crochet.internal.tasks.RemapModsConfigMaker; -import dev.lukebemish.crochet.internal.tasks.RemapModsSourcesConfigMaker; import dev.lukebemish.crochet.internal.tasks.TaskGraphExecution; import dev.lukebemish.crochet.internal.tasks.WriteFile; import dev.lukebemish.taskgraphrunner.model.conversion.SingleVersionGenerator; @@ -20,50 +18,42 @@ import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; -import org.gradle.api.artifacts.ModuleDependency; -import org.gradle.api.artifacts.ProjectDependency; -import org.gradle.api.artifacts.type.ArtifactTypeDefinition; +import org.gradle.api.artifacts.ConsumableConfiguration; +import org.gradle.api.artifacts.DependencyScopeConfiguration; +import org.gradle.api.artifacts.ResolvableConfiguration; import org.gradle.api.attributes.Category; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.Directory; import org.gradle.api.file.FileCollection; import org.gradle.api.file.RegularFile; -import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.provider.Provider; import org.gradle.api.tasks.Copy; import org.gradle.api.tasks.Nested; import org.gradle.api.tasks.SourceSet; -import org.gradle.api.tasks.SourceSetContainer; import org.gradle.api.tasks.TaskProvider; -import org.gradle.api.tasks.bundling.AbstractArchiveTask; import org.gradle.api.tasks.bundling.Jar; import javax.inject.Inject; import java.io.File; import java.io.IOException; -import java.io.Serializable; import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.SequencedSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; -import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; -import static dev.lukebemish.crochet.internal.ConfigurationUtils.copyAttributes; - public abstract class FabricInstallation extends AbstractVanillaInstallation { static final String ACCESS_WIDENER_CATEGORY = "accesswidener"; - final Configuration loaderConfiguration; - final Configuration intermediaryMinecraft; + final DependencyScopeConfiguration loaderConfiguration; + final DependencyScopeConfiguration intermediaryMinecraft; final Configuration mappingsClasspath; final FileCollection writeLog4jConfig; final FabricInstallationArtifacts fabricConfigMaker; @@ -72,16 +62,18 @@ public abstract class FabricInstallation extends AbstractVanillaInstallation { private final TaskProvider intermediaryToNamed; private final TaskProvider namedToIntermediary; final Configuration accessWideners; - final Configuration accessWidenersElements; + final Configuration accessWidenersPath; + final Supplier accessWidenersElements; final TaskProvider extractFabricForDependencies; - final Configuration installationModCompileOnly; - final Configuration installationModCompileOnlyApi; - final Configuration installationModRuntimeOnly; - final Configuration installationModLocalRuntime; - final Configuration installationModLocalImplementation; - final Configuration installationModImplementation; - final Configuration installationModApi; + final DependencyScopeConfiguration compileRemappedDependencies; + final Supplier compileExclude; + final Supplier compileRemapped; + final DependencyScopeConfiguration runtimeRemappedDependencies; + final Supplier runtimeExclude; + final Supplier runtimeRemapped; + + private final FabricInstallationLogic logic; @SuppressWarnings("UnstableApiUsage") @Inject @@ -90,7 +82,7 @@ public FabricInstallation(String name, CrochetExtension extension) { this.extension = extension; - var writeLog4jConfigTask = project.getTasks().register("writeCrochet"+StringUtils.capitalize(name)+"Log4jConfig", WriteFile.class, task -> { + var writeLog4jConfigTask = TaskUtils.registerInternal(this, WriteFile.class, name, "writeLog4jConfig", task -> { task.getContents().convention( Log4jSetup.FABRIC_CONFIG ); @@ -102,12 +94,12 @@ public FabricInstallation(String name, CrochetExtension extension) { this.fabricConfigMaker = project.getObjects().newInstance(FabricInstallationArtifacts.class); fabricConfigMaker.getWrapped().set(vanillaConfigMaker); - this.extractFabricForDependencies = project.getTasks().register("crochet"+StringUtils.capitalize(getName())+"ExtractForDependencies", ExtractFabricDependencies.class, task -> { + this.extractFabricForDependencies = TaskUtils.registerInternal(this, ExtractFabricDependencies.class, name, "extractFromFabricDependencies", task -> { task.getOutputDirectory().set(workingDirectory.get().dir("extracted")); }); fabricConfigMaker.getAccessWideners().from(project.fileTree(extractFabricForDependencies.flatMap(ExtractFabricDependencies::getOutputDirectory)).builtBy(extractFabricForDependencies).filter(it -> it.getName().endsWith(".accesswidener"))); fabricConfigMaker.getInterfaceInjection().from(project.fileTree(extractFabricForDependencies.flatMap(ExtractFabricDependencies::getOutputDirectory)).builtBy(extractFabricForDependencies).filter(it -> it.getName().equals("interface_injections.json"))); - project.getDependencies().add(this.injectedInterfaces.get().getName(), project.fileTree(extractFabricForDependencies.flatMap(ExtractFabricDependencies::getOutputDirectory)).builtBy(extractFabricForDependencies).filter(it -> it.getName().equals("neo_interface_injections.json"))); + project.getDependencies().add(this.injectedInterfaces.getName(), project.fileTree(extractFabricForDependencies.flatMap(ExtractFabricDependencies::getOutputDirectory)).builtBy(extractFabricForDependencies).filter(it -> it.getName().equals("neo_interface_injections.json"))); var intermediaryToNamedFile = workingDirectory.map(dir -> dir.file("runner-intermediary-to-named.tiny")); var namedToIntermediaryFile = workingDirectory.map(dir -> dir.file("runner-named-to-intermediary.tiny")); @@ -118,17 +110,25 @@ public FabricInstallation(String name, CrochetExtension extension) { task.getConfigMaker().set(fabricConfigMaker); }); - this.loaderConfiguration = project.getConfigurations().maybeCreate(getName()+"FabricLoader"); - this.loaderConfiguration.fromDependencyCollector(getDependencies().getLoader()); + this.loaderConfiguration = ConfigurationUtils.dependencyScope(this, name, null, "loader", c -> { + c.fromDependencyCollector(getDependencies().getLoader()); + }); +; this.nonUpgradableDependencies.extendsFrom(this.loaderConfiguration); this.getDependencies().getIntermediary().add(project.provider(() -> this.getDependencies().module("net.fabricmc", "intermediary", this.getMinecraft().get())) ); - var intermediaryConfiguration = project.getConfigurations().maybeCreate(getName()+"Intermediary"); - intermediaryConfiguration.fromDependencyCollector(getDependencies().getIntermediary()); - var intermediaryMappings = project.getTasks().register("crochetExtract"+StringUtils.capitalize(name)+"IntermediaryMappings", Copy.class, task -> { - task.from(project.zipTree(intermediaryConfiguration.getSingleFile())); + + var intermediaryConfiguration = ConfigurationUtils.dependencyScopeInternal(this, name, "intermediary", c -> { + c.fromDependencyCollector(getDependencies().getIntermediary()); + }); + var intermediaryConfigurationClasspath = ConfigurationUtils.resolvableInternal(this, name, "intermediaryClasspath", c -> { + c.extendsFrom(intermediaryConfiguration); + }); + + var intermediaryMappings = TaskUtils.registerInternal(this, Copy.class, name, "extractIntermediaryMappings", task -> { + task.from(project.zipTree(intermediaryConfigurationClasspath.getSingleFile())); task.setDestinationDir(workingDirectory.get().dir("intermediary").getAsFile()); }); @@ -137,17 +137,19 @@ public FabricInstallation(String name, CrochetExtension extension) { task.dependsOn(intermediaryMappings); }); - this.accessWideners = project.getConfigurations().register(name+"AccessWideners", config -> { - config.fromDependencyCollector(getDependencies().getAccessWideners()); - config.setCanBeConsumed(false); - }).get(); - this.accessWidenersElements = project.getConfigurations().register(name+"AccessWidenersElements", config -> { - config.setCanBeResolved(false); - config.setCanBeDeclared(false); - config.setCanBeConsumed(false); - config.extendsFrom(this.accessWideners); - }).get(); - fabricConfigMaker.getAccessWideners().from(accessWideners); + this.accessWideners = ConfigurationUtils.dependencyScope(this, name, null, "accessWideners", c -> { + c.fromDependencyCollector(getDependencies().getAccessWideners()); + }); + this.accessWidenersElements = Suppliers.memoize(() -> ConfigurationUtils.consumable(this, name, null, "accessWidenersElements", c -> { + c.extendsFrom(accessWideners); + })); + this.accessWidenersPath = ConfigurationUtils.resolvableInternal(this, name, "accessWidenersPath", c -> { + c.extendsFrom(accessWideners); + c.attributes(attributes -> + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, ACCESS_WIDENER_CATEGORY)) + ); + }); + fabricConfigMaker.getAccessWideners().from(accessWidenersPath); var intermediaryJar = workingDirectory.map(it -> it.file("intermediary.jar")); this.binaryArtifactsTask.configure(task -> { @@ -156,9 +158,7 @@ public FabricInstallation(String name, CrochetExtension extension) { // To remap dependencies, we need a intermediary -> mojmaps + srg mapping. // We have intermediary -> official from intermediary, and official -> srg + mojmaps from neoform - var objects = project.getObjects(); - - this.intermediaryToNamed = project.getTasks().register("crochet"+StringUtils.capitalize(name)+"IntermediaryToNamed", MappingsWriter.class, task -> { + this.intermediaryToNamed = TaskUtils.registerInternal(this, MappingsWriter.class, name, "intermediaryToNamed", task -> { task.getInputMappings().from(intermediaryToNamedFile); task.dependsOn(this.binaryArtifactsTask); task.getTargetFormat().set(IMappingFile.Format.TINY); @@ -179,7 +179,7 @@ public FabricInstallation(String name, CrochetExtension extension) { }); }); - this.namedToIntermediary = project.getTasks().register("crochet"+StringUtils.capitalize(name)+"NamedToIntermediary", MappingsWriter.class, task -> { + this.namedToIntermediary = TaskUtils.registerInternal(this, MappingsWriter.class, name, "namedToIntermediary", task -> { task.getInputMappings().from(namedToIntermediaryFile); task.dependsOn(this.binaryArtifactsTask); task.getTargetFormat().set(IMappingFile.Format.TINY); @@ -200,8 +200,11 @@ public FabricInstallation(String name, CrochetExtension extension) { }); }); - this.mappingsClasspath = project.getConfigurations().maybeCreate("crochet"+StringUtils.capitalize(getName())+"MappingsClasspath"); - var mappingsJar = project.getTasks().register("crochet"+StringUtils.capitalize(getName())+"MappingsJar", Jar.class, task -> { + var mappings = ConfigurationUtils.dependencyScopeInternal(this, name, "mappings", c -> {}); + this.mappingsClasspath = ConfigurationUtils.resolvableInternal(this, name, "mappingsClasspath", c -> { + c.extendsFrom(mappings); + }); + var mappingsJar = TaskUtils.registerInternal(this, Jar.class, name, "mappingsJar", task -> { task.getDestinationDirectory().set(workingDirectory); task.getArchiveFileName().set("intermediary-mappings.jar"); task.from(intermediaryToNamed.flatMap(MappingsWriter::getOutputMappings), spec -> { @@ -212,13 +215,11 @@ public FabricInstallation(String name, CrochetExtension extension) { }); var mappingsJarFiles = project.files(mappingsJar.map(Jar::getArchiveFile)); mappingsJarFiles.builtBy(mappingsJar); - project.getDependencies().add(mappingsClasspath.getName(), mappingsJarFiles); + project.getDependencies().add(mappings.getName(), mappingsJarFiles); - this.intermediaryMinecraft = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"IntermediaryMinecraft", config -> { - config.setCanBeConsumed(false); + this.intermediaryMinecraft = ConfigurationUtils.dependencyScopeInternal(this, name, "intermediaryMinecraft", config -> { config.extendsFrom(minecraftDependencies); config.extendsFrom(minecraftResources); - config.attributes(attributes -> attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, getDistribution().map(InstallationDistribution::neoAttributeValue))); }); var intermediaryJarFiles = project.files(); @@ -226,13 +227,112 @@ public FabricInstallation(String name, CrochetExtension extension) { intermediaryJarFiles.builtBy(this.binaryArtifactsTask); project.getDependencies().add(intermediaryMinecraft.getName(), intermediaryJarFiles); - this.installationModCompileOnly = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"ModCompileOnly").get(); - this.installationModCompileOnlyApi = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"ModCompileOnlyApi").get(); - this.installationModRuntimeOnly = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"ModRuntimeOnly").get(); - this.installationModLocalRuntime = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"ModLocalRuntime").get(); - this.installationModLocalImplementation = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"ModLocalImplementation").get(); - this.installationModImplementation = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"ModImplementation").get(); - this.installationModApi = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"ModApi").get(); + this.compileRemappedDependencies = ConfigurationUtils.dependencyScopeInternal(this, name, "compileRemappedDependencies", config -> {}); + this.compileRemapped = Suppliers.memoize(() -> ConfigurationUtils.consumableInternal(this, name, "compileRemapped", config -> { + var group = CROSS_PROJECT_BUNDLE_CAPABILITY_GROUP + sharingInstallationTypeTag(); + config.getOutgoing().capability(group + ":" + name + "-compile-remapped" + ":" + "1.0.0"); + config.getAttributes().attributeProvider(CrochetProjectPlugin.LOCAL_DISTRIBUTION_ATTRIBUTE, getDistribution().map(it -> it.name().toLowerCase(Locale.ROOT))); + config.extendsFrom(compileRemappedDependencies); + })); + this.compileExclude = Suppliers.memoize(() -> ConfigurationUtils.consumableInternal(this, name, "compileExclude", config -> { + var group = CROSS_PROJECT_BUNDLE_CAPABILITY_GROUP + sharingInstallationTypeTag(); + config.getOutgoing().capability(group + ":" + name + "-compile-exclude" + ":" + "1.0.0"); + config.getAttributes().attributeProvider(CrochetProjectPlugin.LOCAL_DISTRIBUTION_ATTRIBUTE, getDistribution().map(it -> it.name().toLowerCase(Locale.ROOT))); + })); + this.runtimeRemappedDependencies = ConfigurationUtils.dependencyScopeInternal(this, name, "runtimeRemappedDependencies", config -> {}); + this.runtimeRemapped = Suppliers.memoize(() -> ConfigurationUtils.consumableInternal(this, name, "runtimeRemapped", config -> { + var group = CROSS_PROJECT_BUNDLE_CAPABILITY_GROUP + sharingInstallationTypeTag(); + config.getOutgoing().capability(group + ":" + name + "-runtime-remapped" + ":" + "1.0.0"); + config.getAttributes().attributeProvider(CrochetProjectPlugin.LOCAL_DISTRIBUTION_ATTRIBUTE, getDistribution().map(it -> it.name().toLowerCase(Locale.ROOT))); + config.extendsFrom(runtimeRemappedDependencies); + })); + this.runtimeExclude = Suppliers.memoize(() -> ConfigurationUtils.consumableInternal(this, name, "runtimeExclude", config -> { + var group = CROSS_PROJECT_BUNDLE_CAPABILITY_GROUP + sharingInstallationTypeTag(); + config.getOutgoing().capability(group + ":" + name + "-runtime-exclude" + ":" + "1.0.0"); + config.getAttributes().attributeProvider(CrochetProjectPlugin.LOCAL_DISTRIBUTION_ATTRIBUTE, getDistribution().map(it -> it.name().toLowerCase(Locale.ROOT))); + })); + + this.logic = new FabricInstallationLogic(this, project) { + @Override + protected DependencyScopeConfiguration loaderConfiguration() { + return loaderConfiguration; + } + + @Override + protected DependencyScopeConfiguration intermediaryMinecraft() { + return intermediaryMinecraft; + } + + @Override + protected void extractFabricForDependencies(ResolvableConfiguration modCompileClasspath, ResolvableConfiguration modRuntimeClasspath) { + FabricInstallation.this.extractFabricForDependencies.configure(task -> { + task.getCompileModJars().from(modCompileClasspath); + task.getRuntimeModJars().from(modRuntimeClasspath); + }); + } + + @Override + protected Provider namedToIntermediary() { + return namedToIntermediary.flatMap(MappingsWriter::getOutputMappings); + } + + @Override + protected Provider intermediaryToNamed() { + return intermediaryToNamed.flatMap(MappingsWriter::getOutputMappings); + } + + @Override + protected FileCollection namedToIntermediaryFlat() { + var files = project.files(); + files.from(namedToIntermediary.flatMap(MappingsWriter::getOutputMappings)); + files.builtBy(namedToIntermediary); + return files; + } + + @Override + protected FileCollection intermediaryToNamedFlat() { + var files = project.files(); + files.from(intermediaryToNamed.flatMap(MappingsWriter::getOutputMappings)); + files.builtBy(intermediaryToNamed); + return files; + } + + @Override + protected Configuration compileExclude() { + return null; + } + + @Override + protected Configuration runtimeExclude() { + return null; + } + + @Override + protected Configuration compileRemapped() { + return null; + } + + @Override + protected Configuration runtimeRemapped() { + return null; + } + + @Override + protected void includeInterfaceInjections(ConfigurableFileCollection interfaceInjectionFiles) { + interfaceInjectionFiles.from(injectedInterfacesElements.get().getOutgoing().getArtifacts().getFiles()); + interfaceInjectionFiles.builtBy(injectedInterfacesElements.get().getOutgoing().getArtifacts().getBuildDependencies()); + } + + @Override + protected Provider workingDirectory() { + return workingDirectory; + } + + @Override + protected void addArtifacts(TaskGraphExecution task) { + task.artifactsConfiguration(project.getConfigurations().getByName(CrochetProjectPlugin.TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME)); + } + }; } @Override @@ -247,12 +347,22 @@ public void forLocalFeature(SourceSet sourceSet) { public void forFeature(SourceSet sourceSet, Action action) { super.forFeature(sourceSet); - forFeatureShared(sourceSet, action, false); + forFeatureShared(sourceSet, deps -> { + for (var bundleAction : bundleActions) { + bundleAction.execute(deps); + } + action.execute(deps); + }, false); } public void forLocalFeature(SourceSet sourceSet, Action action) { super.forLocalFeature(sourceSet); - forFeatureShared(sourceSet, action, true); + forFeatureShared(sourceSet, deps -> { + for (var bundleAction : bundleActions) { + bundleAction.execute(deps); + } + action.execute(deps); + }, true); } @Override @@ -262,539 +372,39 @@ protected List getInstallationConfigurationNames() { return out; } - private Configuration forRunRemapping(Run run, RunType runType) { - /* - General architecture - - run.classpath -- the normal run classpath. We make it select "not-to-remap" dependencies - - modClasspath -- collect dependencies for remapping. Resolve remap type "to-remap" - - remappedClasspath -- output of remapping, used to insert stuff into run classpath - - To exclude deps, we make another classpath: - - excludedClasspath -- run.classpath with only project/module dependencies - - And we still need to pin versions: - - versioningClasspath - And have every resolving configuration, run.classpath and modClasspath, be shouldResolveConsistentlyWith it - */ - - var runClasspath = run.classpath; - - var modClasspath = project.getConfigurations().register("crochet"+StringUtils.capitalize(run.getName())+"RunModClasspath", config -> { - config.attributes(attributes -> { - copyAttributes(runClasspath.getAttributes(), attributes, project.getProviders()); - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); - attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); - }); - config.setCanBeConsumed(false); - }).get(); - modClasspath.fromDependencyCollector(run.getImplementation()); - runClasspath.fromDependencyCollector(run.getImplementation()); - - var remappedClasspath = project.getConfigurations().maybeCreate("crochet"+StringUtils.capitalize(run.getName())+"RunRemappedModClasspath"); - - var excludedClasspath = project.getConfigurations().register("crochet"+StringUtils.capitalize(run.getName())+"RunExcludedClasspath", config -> { - config.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); - copyAttributes(runClasspath.getAttributes(), attributes, project.getProviders()); - }); - config.setCanBeConsumed(false); - }).get(); - excludedClasspath.fromDependencyCollector(run.getImplementation()); - - var versioningClasspath = project.getConfigurations().register("crochet"+StringUtils.capitalize(run.getName())+"RunVersioningClasspath", config -> { - config.attributes(attributes -> { - copyAttributes(runClasspath.getAttributes(), attributes, project.getProviders()); - }); - config.shouldResolveConsistentlyWith(switch (runType) { - case CLIENT -> nonUpgradableClientRuntimeDependencies; - case SERVER -> nonUpgradableServerRuntimeDependencies; - case DATA -> nonUpgradableClientRuntimeDependencies; - }); - config.setCanBeConsumed(false); - }).get(); - - versioningClasspath.extendsFrom(modClasspath); - versioningClasspath.extendsFrom(runClasspath); - runClasspath.shouldResolveConsistentlyWith(versioningClasspath); - modClasspath.shouldResolveConsistentlyWith(versioningClasspath); - excludedClasspath.shouldResolveConsistentlyWith(versioningClasspath); - - runClasspath.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); - }); - - run.classpath.extendsFrom(remappedClasspath); - - var remappingClasspath = project.getConfigurations().register("crochet"+StringUtils.capitalize(run.getName())+"RunRemappingClasspath", config -> { - config.attributes(attributes -> { - copyAttributes(runClasspath.getAttributes(), attributes, project.getProviders()); - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); - attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); - }); - config.setCanBeConsumed(false); - }).get(); - remappingClasspath.extendsFrom(modClasspath); - - remappingClasspath.extendsFrom(modClasspath); - remappingClasspath.extendsFrom(intermediaryMinecraft); - remappingClasspath.shouldResolveConsistentlyWith(versioningClasspath); - - var remappedMods = project.files(); - project.getDependencies().add(remappedClasspath.getName(), remappedMods); - - var remapMods = project.getTasks().register("crochetRemap"+StringUtils.capitalize(run.getName())+"RunClasspath", TaskGraphExecution.class, task -> { - var configMaker = project.getObjects().newInstance(RemapModsConfigMaker.class); - configMaker.setup(task, modClasspath, excludedClasspath, workingDirectory.get().dir("runClasspath").dir(run.getName()), remappedMods); - task.dependsOn(intermediaryToNamed); - configMaker.getDistribution().set(getDistribution()); - configMaker.getRemappingClasspath().from(remappingClasspath); - configMaker.getMappings().set(intermediaryToNamed.flatMap(MappingsWriter::getOutputMappings)); - task.getConfigMaker().set(configMaker); - - task.copyArtifactsFrom(this.binaryArtifactsTask.get()); - task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); - }); - remappedMods.builtBy(remapMods); - - var remapModSources = project.getTasks().register("crochetRemap"+StringUtils.capitalize(run.getName())+"RunClasspathSources", TaskGraphExecution.class, task -> { - var configMaker = project.getObjects().newInstance(RemapModsSourcesConfigMaker.class); - configMaker.setup(task, modClasspath, excludedClasspath, workingDirectory.get().dir("runClasspathSources").dir(run.getName())); - task.dependsOn(intermediaryToNamed); - configMaker.getRemappingClasspath().from(remappingClasspath); - configMaker.getMappings().set(intermediaryToNamed.flatMap(MappingsWriter::getOutputMappings)); - task.getConfigMaker().set(configMaker); - - task.copyArtifactsFrom(this.binaryArtifactsTask.get()); - task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); - }); - - linkSources(remapMods, remapModSources); - - extension.idePostSync.configure(task -> { - task.dependsOn(remapMods); - - task.dependsOn(remapModSources); - }); - - return remappingClasspath; + void makeBundle(FabricDependencyBundle bundle) { + FabricRemapDependencies dependencies = project.getObjects().newInstance(FabricRemapDependencies.class); + bundle.action.execute(dependencies); + this.bundleActions.add(bundle.action); + + logic.forNamedBundle( + "crochetBundle"+StringUtils.capitalize(bundle.getName()), + dependencies, + compileRemappedDependencies, + compileExclude.get(), + runtimeRemappedDependencies, + runtimeExclude.get() + ); + compileRemapped.get(); + runtimeRemapped.get(); } - private static final List MOD_CONFIGURATION_NAMES = List.of( - "compileOnly", - "compileOnlyApi", - "runtimeOnly", - "implementation", - "api", - "localRuntime", - "localImplementation" - ); + private final List> bundleActions = new ArrayList<>(); - @SuppressWarnings("UnstableApiUsage") private void forFeatureShared(SourceSet sourceSet, Action action, boolean local) { - /* - General architecture: - - compileClasspath -- the normal classpath for the mod. Specifically selects "not-to-remap" dependencies - - modCompileClasspath and modRuntimeClasspath -- collect dependencies for remapping. Resolve remap type "to-remap" - - modApiElements and modRuntimeElements -- remap type "to-remap", these expose to-remap dependencies, but no artifacts, transitively - - nonModApiElements and nonModRuntimeElements -- remap type "not-ro-remap", these expose the non-remapped project artifacts/variants, as well as any non-remapped deps, transitively - - remappedCompileClasspath -- output of remapping, used to insert stuff into compile classpath - - ATs and IIs are grabbed from anything that is on both modRuntime and modCompile classpaths - TODO: see if we can limit that to only care about compile by having a separate MC jar for runtime - - Remapping is set up here only for the compile classpath. The excluded deps should be anything on compileClasspath that _isn't_ the remapped dependencies (any project/module deps, perhaps?) - To handle exclusions, we make another classpath: - - excludedCompileClasspath -- compileClasspath with only project/module dependencies - - And finally, to handle versioning right, we make a version-determining classpath: - - versioningCompileClasspath - And have every resolving configuration, compileClasspath and modCompileClasspath, be shouldResolveConsistentlyWith it - - If this is non-local - - remapJar, remapSourcesJar -- remapped jar dependencies - */ - - project.getConfigurations().named(sourceSet.getTaskName(null, JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME), config -> { - config.extendsFrom(loaderConfiguration); - }); - - var dependencies = project.getObjects().newInstance(FabricSourceSetDependencies.class); - action.execute(dependencies); - - var modCompileClasspath = project.getConfigurations().register(sourceSet.getTaskName("crochetMod", "compileClasspath"), config -> { - config.attributes(attributes -> { - copyAttributes(project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); - attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); - }); - config.setCanBeDeclared(false); - config.setCanBeConsumed(false); - }).get(); - var modRuntimeClasspath = project.getConfigurations().register(sourceSet.getTaskName("crochetMod", "runtimeClasspath"), config -> { - config.attributes(attributes -> { - copyAttributes(project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); - attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); - }); - config.setCanBeDeclared(false); - config.setCanBeConsumed(false); - }).get(); - - var versioningCompileClasspath = project.getConfigurations().register(sourceSet.getTaskName("crochetVersioning", "compileClasspath"), config -> { - config.attributes(attributes -> { - // Does not have the remap type attribute at this point - copyAttributes(project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); - }); - config.setCanBeConsumed(false); - config.shouldResolveConsistentlyWith(switch (getDistribution().get()) { - case CLIENT, JOINED -> nonUpgradableClientCompileDependencies; - case SERVER, COMMON -> nonUpgradableServerCompileDependencies; - }); - }).get(); - - var excludedCompileClasspath = project.getConfigurations().register(sourceSet.getTaskName("crochetExcluded", "compileClasspath"), config -> { - config.attributes(attributes -> { - copyAttributes(project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); - }); - config.setCanBeConsumed(false); - }).get(); - - var compileClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()); - excludedCompileClasspath.getDependencyConstraints().addAllLater(project.provider(compileClasspath::getDependencyConstraints)); - // Only non-file-collection dependencies - excludedCompileClasspath.getDependencies().addAllLater(project.provider(compileClasspath::getAllDependencies).map(deps -> new ArrayList<>(deps.stream().filter(dep -> (dep instanceof ModuleDependency)).toList()))); - - versioningCompileClasspath.extendsFrom(modCompileClasspath); - versioningCompileClasspath.extendsFrom(compileClasspath); - compileClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); - modCompileClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); - excludedCompileClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); - - this.extractFabricForDependencies.configure(task -> { - task.getCompileModJars().from(modCompileClasspath); - task.getRuntimeModJars().from(modRuntimeClasspath); - }); - - var runtimeElements = project.getConfigurations().maybeCreate(sourceSet.getRuntimeElementsConfigurationName()); - var apiElements = project.getConfigurations().maybeCreate(sourceSet.getApiElementsConfigurationName()); - - var modRuntimeElements = project.getConfigurations().register(sourceSet.getTaskName("crochetMod", "runtimeElements")).get(); - modRuntimeElements.setCanBeDeclared(false); - var modApiElements = project.getConfigurations().register(sourceSet.getTaskName("crochetMod", "apiElements")).get(); - modApiElements.setCanBeDeclared(false); - - var nonModRuntimeElements = project.getConfigurations().maybeCreate(sourceSet.getTaskName("crochetNonMod", "runtimeElements")); - nonModRuntimeElements.setCanBeDeclared(false); - nonModRuntimeElements.setCanBeResolved(false); - var nonModApiElements = project.getConfigurations().maybeCreate(sourceSet.getTaskName("crochetNonMod", "apiElements")); - nonModApiElements.setCanBeDeclared(false); - nonModApiElements.setCanBeResolved(false); - FeatureUtils.forSourceSetFeature(project, sourceSet.getName(), context -> { - context.withCapabilities(accessWidenersElements); - accessWidenersElements.setCanBeConsumed(true); - accessWidenersElements.attributes(attributes -> { + context.withCapabilities(accessWidenersElements.get()); + accessWidenersElements.get().attributes(attributes -> { attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, ACCESS_WIDENER_CATEGORY)); }); - - context.withCapabilities(modRuntimeElements); - context.withCapabilities(modApiElements); - context.withCapabilities(nonModRuntimeElements); - context.withCapabilities(nonModApiElements); - - modRuntimeElements.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - copyAttributes(runtimeElements.getAttributes(), attributes, project.getProviders()); - }); - modRuntimeElements.setCanBeConsumed(true); - modRuntimeElements.setCanBeResolved(false); - - nonModRuntimeElements.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - copyAttributes(runtimeElements.getAttributes(), attributes, project.getProviders()); - }); - nonModRuntimeElements.setCanBeConsumed(true); - nonModRuntimeElements.setCanBeResolved(false); - - modApiElements.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - copyAttributes(apiElements.getAttributes(), attributes, project.getProviders()); - }); - modApiElements.setCanBeConsumed(true); - modApiElements.setCanBeResolved(false); - - nonModApiElements.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - copyAttributes(apiElements.getAttributes(), attributes, project.getProviders()); - }); - nonModApiElements.setCanBeConsumed(true); - nonModApiElements.setCanBeResolved(false); - - nonModApiElements.getDependencyConstraints().addAllLater(project.provider(apiElements::getDependencyConstraints)); - nonModRuntimeElements.getDependencyConstraints().addAllLater(project.provider(runtimeElements::getDependencyConstraints)); - - nonModApiElements.getDependencies().addAllLater(project.provider(() -> - apiElements.getAllDependencies().stream().filter(dep -> (dep instanceof ProjectDependency) || !modCompileClasspath.getAllDependencies().contains(dep)).toList() - )); - nonModRuntimeElements.getDependencies().addAllLater(project.provider(() -> - runtimeElements.getAllDependencies().stream().filter(dep -> (dep instanceof ProjectDependency) || !modRuntimeClasspath.getAllDependencies().contains(dep)).toList() - )); - - var remappedSourcesElements = project.getConfigurations().maybeCreate(sourceSet.getTaskName("crochetRemapped", "sourcesElements")); - - if (!local) { - { - // Remap jar - var jarTask = project.getTasks().named(sourceSet.getJarTaskName(), Jar.class); - - AtomicBoolean hasSetup = new AtomicBoolean(false); - AtomicReference classifier = new AtomicReference<>(); - AtomicReference oldJarLocation = new AtomicReference<>(); - - BiConsumer configurator = (remapJar, jar) -> { - if (hasSetup.getAndSet(true)) { - return; - } - var configMaker = project.getObjects().newInstance(RemapModsConfigMaker.class); - var oldArchiveFile = jarTask.get().getArchiveFile().get(); - var existingClassifier = jarTask.get().getArchiveClassifier().get(); - - classifier.set(existingClassifier); - oldJarLocation.set(oldArchiveFile); - - jarTask.get().getArchiveClassifier().set(existingClassifier.isEmpty() ? "dev" : existingClassifier + "-dev"); - var remappingClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getIncoming().artifactView(view -> view.attributes(attributes -> - attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE) - )).getFiles(); - configMaker.remapSingleJar(remapJar, input -> { - input.set(jarTask.flatMap(AbstractArchiveTask::getArchiveFile)); - }, output -> { - output.set(oldArchiveFile); - }, mappings -> { - }, remappingClasspath); - // Change direction and whatnot - configMaker.getIsReObf().set(true); - - configMaker.getStripNestedJars().set(false); - configMaker.getIncludedInterfaceInjections().from(injectedInterfacesElements.get().getOutgoing().getArtifacts().getFiles()); - - configMaker.getMappings().set(namedToIntermediary.flatMap(MappingsWriter::getOutputMappings)); - remapJar.getConfigMaker().set(configMaker); - - remapJar.artifactsConfiguration(project.getConfigurations().getByName(CrochetProjectPlugin.TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME)); - remapJar.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); - remapJar.dependsOn(jarTask); - - // Set up mixin remapping flags via jar manifest - jar.manifest(m -> m.attributes(Map.of( - "Fabric-Loom-Mixin-Remap-Type", "STATIC" - ))); - }; - - var remapJarTask = project.getTasks().register(sourceSet.getTaskName("remap", "jar"), TaskGraphExecution.class, task -> { - configurator.accept(task, jarTask.get()); - }); - - jarTask.configure(task -> { - configurator.accept(remapJarTask.get(), task); - }); - - project.getTasks().named("build", t -> t.dependsOn(remapJarTask)); - - nonModRuntimeElements.getOutgoing().getArtifacts().addAll(runtimeElements.getAllArtifacts()); - runtimeElements.getOutgoing().getArtifacts().clear(); - nonModApiElements.getOutgoing().getArtifacts().addAll(apiElements.getAllArtifacts()); - apiElements.getOutgoing().getArtifacts().clear(); - - project.artifacts(artifacts -> { - configurator.accept(remapJarTask.get(), jarTask.get()); - artifacts.add(runtimeElements.getName(), oldJarLocation.get(), spec -> { - spec.setClassifier(classifier.get()); - spec.setType(ArtifactTypeDefinition.JAR_TYPE); - spec.builtBy(remapJarTask); - }); - artifacts.add(apiElements.getName(), oldJarLocation.get(), spec -> { - spec.setClassifier(classifier.get()); - spec.setType(ArtifactTypeDefinition.JAR_TYPE); - spec.builtBy(remapJarTask); - }); - }); - } - - { - // Remap sources jar - AtomicBoolean hasSetup = new AtomicBoolean(false); - AtomicReference classifier = new AtomicReference<>(); - AtomicReference oldJarLocation = new AtomicReference<>(); - - BiConsumer configurator = (remapSourcesJar, sourcesJar) -> { - if (hasSetup.getAndSet(true)) { - return; - } - var configMaker = project.getObjects().newInstance(RemapModsSourcesConfigMaker.class); - var oldArchiveFile = sourcesJar.getArchiveFile().get(); - var existingClassifier = sourcesJar.getArchiveClassifier().get(); - - classifier.set(existingClassifier); - oldJarLocation.set(oldArchiveFile); - - sourcesJar.getArchiveClassifier().set(existingClassifier.isEmpty() ? "dev" : existingClassifier + "-dev"); - var remappingClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getIncoming().artifactView(view -> view.attributes(attributes -> - attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE) - )).getFiles(); - configMaker.remapSingleJar(remapSourcesJar, input -> { - input.set(sourcesJar.getArchiveFile()); - }, output -> { - output.set(oldArchiveFile); - }, mappings -> { - }, remappingClasspath); - - configMaker.getMappings().set(namedToIntermediary.flatMap(MappingsWriter::getOutputMappings)); - remapSourcesJar.getConfigMaker().set(configMaker); - - remapSourcesJar.artifactsConfiguration(project.getConfigurations().getByName(CrochetProjectPlugin.TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME)); - remapSourcesJar.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); - remapSourcesJar.dependsOn(sourcesJar); - }; - - AtomicBoolean registered = new AtomicBoolean(false); - - var remapTaskName = sourceSet.getTaskName("remap", "SourcesJar"); - - Consumer maybeConfigure = jarTask -> { - if (registered.compareAndSet(false, true)) { - var remapTask = project.getTasks().register(remapTaskName, TaskGraphExecution.class); - configurator.accept(remapTask.get(), jarTask); - project.getTasks().named("build", t -> t.dependsOn(remapTask)); - - var sourcesElements = project.getConfigurations().maybeCreate(sourceSet.getSourcesElementsConfigurationName()); - sourcesElements.getOutgoing().getArtifacts().clear(); - - // An empty configuration, so that the RemapModsSourcesConfigMaker will skip the actual sources jar - remappedSourcesElements.attributes(attributes -> { - copyAttributes(sourcesElements.getAttributes(), attributes, project.getProviders()); - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); - }); - remappedSourcesElements.setCanBeResolved(false); - remappedSourcesElements.setCanBeConsumed(true); - remappedSourcesElements.setCanBeDeclared(false); - - project.artifacts(artifacts -> { - artifacts.add(sourcesElements.getName(), oldJarLocation.get(), spec -> { - spec.setClassifier(classifier.get()); - spec.setType(ArtifactTypeDefinition.JAR_TYPE); - spec.builtBy(remapTask); - }); - }); - } - }; - - project.getTasks().withType(Jar.class, sourceJarTask -> { - if (sourceJarTask.getName().equals(sourceSet.getSourcesJarTaskName())) { - maybeConfigure.accept(sourceJarTask); - } - }); - - project.afterEvaluate(p -> { - var sourceJarTask = project.getTasks().findByName(sourceSet.getSourcesJarTaskName()); - if (sourceJarTask instanceof Jar jarTask) { - maybeConfigure.accept(jarTask); - } - }); - } - } else { - nonModApiElements.getOutgoing().getArtifacts().addAllLater(project.provider(apiElements::getAllArtifacts)); - nonModRuntimeElements.getOutgoing().getArtifacts().addAllLater(project.provider(runtimeElements::getAllArtifacts)); - } - apiElements.getOutgoing().getVariants().forEach(variant -> { - var newVariant = nonModApiElements.getOutgoing().getVariants().create(variant.getName()); - copyAttributes(variant.getAttributes(), newVariant.getAttributes(), project.getProviders()); - newVariant.getArtifacts().addAllLater(project.provider(variant::getArtifacts)); - newVariant.getAttributes().attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, dev.lukebemish.crochet.internal.CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); - }); - apiElements.getOutgoing().getVariants().clear(); - runtimeElements.getOutgoing().getVariants().forEach(variant -> { - var newVariant = nonModRuntimeElements.getOutgoing().getVariants().create(variant.getName()); - copyAttributes(variant.getAttributes(), newVariant.getAttributes(), project.getProviders()); - newVariant.getArtifacts().addAllLater(project.provider(variant::getArtifacts)); - newVariant.getAttributes().attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); - }); - runtimeElements.getOutgoing().getVariants().clear(); }); - var modCompileOnly = project.getConfigurations().maybeCreate(sourceSet.getTaskName("mod", "compileOnly")); - modCompileOnly.fromDependencyCollector(dependencies.getModCompileOnly()); - modCompileOnly.extendsFrom(installationModCompileOnly); - modCompileClasspath.extendsFrom(modCompileOnly); - var modCompileOnlyApi = project.getConfigurations().maybeCreate(sourceSet.getTaskName("mod", "compileOnlyApi")); - modCompileOnlyApi.fromDependencyCollector(dependencies.getModCompileOnlyApi()); - modCompileOnlyApi.extendsFrom(installationModCompileOnlyApi); - modCompileClasspath.extendsFrom(modCompileOnlyApi); - apiElements.extendsFrom(modCompileOnlyApi); - modApiElements.extendsFrom(modCompileOnlyApi); - var modRuntimeOnly = project.getConfigurations().maybeCreate(sourceSet.getTaskName("mod", "runtimeOnly")); - modRuntimeOnly.fromDependencyCollector(dependencies.getModRuntimeOnly()); - modRuntimeOnly.extendsFrom(installationModRuntimeOnly); - modRuntimeClasspath.extendsFrom(modRuntimeOnly); - runtimeElements.extendsFrom(modRuntimeOnly); - modRuntimeElements.extendsFrom(modRuntimeOnly); - var modLocalRuntime = project.getConfigurations().maybeCreate(sourceSet.getTaskName("mod", "localRuntime")); - modLocalRuntime.fromDependencyCollector(dependencies.getModLocalRuntime()); - modLocalRuntime.extendsFrom(installationModLocalRuntime); - modRuntimeClasspath.extendsFrom(modLocalRuntime); - var modLocalImplementation = project.getConfigurations().maybeCreate(sourceSet.getTaskName("mod", "localImplementation")); - modLocalImplementation.fromDependencyCollector(dependencies.getModLocalImplementation()); - modLocalImplementation.extendsFrom(installationModLocalImplementation); - modCompileClasspath.extendsFrom(modLocalImplementation); - modRuntimeClasspath.extendsFrom(modLocalImplementation); - var modImplementation = project.getConfigurations().maybeCreate(sourceSet.getTaskName("mod", "implementation")); - modImplementation.fromDependencyCollector(dependencies.getModImplementation()); - modImplementation.extendsFrom(installationModImplementation); - modCompileClasspath.extendsFrom(modImplementation); - modRuntimeClasspath.extendsFrom(modImplementation); - runtimeElements.extendsFrom(modImplementation); - modRuntimeElements.extendsFrom(modImplementation); - apiElements.extendsFrom(modImplementation); - modApiElements.extendsFrom(modImplementation); - var modApi = project.getConfigurations().maybeCreate(sourceSet.getTaskName("mod", "api")); - modApi.fromDependencyCollector(dependencies.getModApi()); - modApi.extendsFrom(installationModApi); - modCompileClasspath.extendsFrom(modApi); - modRuntimeClasspath.extendsFrom(modApi); - runtimeElements.extendsFrom(modApi); - modRuntimeElements.extendsFrom(modApi); - apiElements.extendsFrom(modApi); - modApiElements.extendsFrom(modApi); - - // Link up inheritance via CrochetFeatureContexts for the injected configurations - var marker = InheritanceMarker.getOrCreate(project.getObjects(), sourceSet); - marker.getShouldTakeConfigurationsFrom().configureEach(name -> { - var otherSourceSet = project.getExtensions().getByType(SourceSetContainer.class).findByName(name); - var otherInstallation = extension.findInstallation(otherSourceSet); - if (otherInstallation instanceof FabricInstallation) { - for (var confName : MOD_CONFIGURATION_NAMES) { - var thisConf = project.getConfigurations().getByName(sourceSet.getTaskName("mod", confName)); - var otherConf = project.getConfigurations().getByName(otherSourceSet.getTaskName("mod", confName)); - thisConf.extendsFrom(otherConf); - } - } - }); - marker.getShouldGiveConfigurationsTo().configureEach(name -> { - var otherSourceSet = project.getExtensions().getByType(SourceSetContainer.class).findByName(name); - var otherInstallation = extension.findInstallation(otherSourceSet); - if (otherInstallation instanceof FabricInstallation) { - for (var confName : MOD_CONFIGURATION_NAMES) { - var thisConf = project.getConfigurations().getByName(sourceSet.getTaskName("mod", confName)); - var otherConf = project.getConfigurations().getByName(otherSourceSet.getTaskName("mod", confName)); - otherConf.extendsFrom(thisConf); - } - } - }); + var dependencies = project.getObjects().newInstance(FabricSourceSetDependencies.class); + action.execute(dependencies); + + var out = logic.forFeatureShared(sourceSet, dependencies, local); + var modCompileClasspath = out.modCompileClasspath(); + var modRuntimeClasspath = out.modRuntimeClasspath(); var interfaceInjectionCompile = modCompileClasspath.getIncoming().artifactView(config -> { config.attributes(attributes -> { @@ -830,105 +440,6 @@ private void forFeatureShared(SourceSet sourceSet, Action { - config.attributes(attributes -> { - copyAttributes(project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); - }); - config.setCanBeConsumed(false); - config.setCanBeDeclared(false); - }).get(); - - project.getConfigurations().named(sourceSet.getCompileClasspathConfigurationName(), config -> { - config.attributes(attributes -> - attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP) - ); - config.getDependencies().addAllLater(project.provider(() -> - modCompileClasspath.getAllDependencies().stream().filter(dep -> dep instanceof ProjectDependency).toList() - )); - }); - - compileRemappingClasspath.extendsFrom(modCompileClasspath); - compileRemappingClasspath.extendsFrom(intermediaryMinecraft); - compileRemappingClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); - - var remappedCompileMods = project.files(); - project.getDependencies().add(remappedCompileClasspath.getName(), remappedCompileMods); - - var remapCompileMods = project.getTasks().register(sourceSet.getTaskName("crochetRemap", "CompileClasspath"), TaskGraphExecution.class, task -> { - var configMaker = project.getObjects().newInstance(RemapModsConfigMaker.class); - configMaker.setup(task, modCompileClasspath, excludedCompileClasspath, workingDirectory.get().dir("compileClasspath").dir(sourceSet.getName()), remappedCompileMods); - task.dependsOn(intermediaryToNamed); - configMaker.getDistribution().set(getDistribution()); - configMaker.getRemappingClasspath().from(compileRemappingClasspath); - configMaker.getMappings().set(intermediaryToNamed.flatMap(MappingsWriter::getOutputMappings)); - task.getConfigMaker().set(configMaker); - - task.copyArtifactsFrom(this.binaryArtifactsTask.get()); - task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); - }); - remappedCompileMods.builtBy(remapCompileMods); - - var remapCompileModSources = project.getTasks().register(sourceSet.getTaskName("crochetRemap", "CompileClasspathSources"), TaskGraphExecution.class, task -> { - var configMaker = project.getObjects().newInstance(RemapModsSourcesConfigMaker.class); - configMaker.setup(task, modCompileClasspath, excludedCompileClasspath, workingDirectory.get().dir("compileClasspathSources").dir(sourceSet.getName())); - task.dependsOn(intermediaryToNamed); - configMaker.getRemappingClasspath().from(compileRemappingClasspath); - configMaker.getMappings().set(intermediaryToNamed.flatMap(MappingsWriter::getOutputMappings)); - task.getConfigMaker().set(configMaker); - - task.copyArtifactsFrom(this.binaryArtifactsTask.get()); - task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); - }); - - linkSources(remapCompileMods, remapCompileModSources); - - extension.idePostSync.configure(task -> { - task.dependsOn(remapCompileMods); - - task.dependsOn(remapCompileModSources); - }); - } - - private void linkSources(TaskProvider remapJars, TaskProvider remapSourceJars) { - record Pair(T first, U second) implements Serializable {} - - if (IdeaModelHandlerPlugin.isIdeaSyncRelated(project)) { - Provider> sources = remapSourceJars.get().getConfigMaker().map(configMaker -> { - var targets = ((RemapModsSourcesConfigMaker) configMaker).getTargets().get(); - Map map = new HashMap<>(); - targets.forEach(target -> target.getCapabilities().get().forEach(cap -> map.put(cap, target))); - return map; - }); - - Provider>> binariesToSources = sources.zip(remapJars.get().getConfigMaker().flatMap(configMaker -> ((RemapModsConfigMaker) configMaker).getTargets()), (sourceMap, binaryTargets) -> { - List> map = new ArrayList<>(); - binaryTargets.forEach(binary -> { - Set sourceTargets = new HashSet<>(); - binary.getCapabilities().get().forEach(cap -> { - ArtifactTarget source = sourceMap.get(cap); - if (source != null) { - sourceTargets.add(source); - } - }); - if (sourceTargets.size() == 1) { - map.add(new Pair<>(binary, sourceTargets.iterator().next())); - } - }); - return map; - }); - - IdeaModelHandlerPlugin.retrieve(project).mapBinariesToSources( - binariesToSources.map(pairs -> pairs.stream().map(p -> p.first().getTarget().get()).toList()), - binariesToSources.map(pairs -> pairs.stream().map(p -> p.second().getTarget().get()).toList()) - ); - } } @Override @@ -936,6 +447,33 @@ protected boolean canPublishInjectedInterfaces() { return false; } + @Override + protected Map makeConfigurationsToLink() { + var map = new LinkedHashMap<>(super.makeConfigurationsToLink()); + map.put("loader", ConfigurationUtils.consumableInternal(this, getName(), "loaderElements", config -> { + config.extendsFrom(loaderConfiguration); + })); + map.put("intermediary", ConfigurationUtils.consumableInternal(this, getName(), "intermediaryMinecraftElements", config -> { + config.extendsFrom(intermediaryMinecraft); + })); + map.put("mappings-intermediary-named", ConfigurationUtils.consumableInternal(this, getName(), "intermediaryToNamedMappings", config -> { + config.getOutgoing().artifact(intermediaryToNamed.flatMap(MappingsWriter::getOutputMappings), artifact -> { + artifact.builtBy(intermediaryToNamed); + }); + })); + map.put("mappings-named-intermediary", ConfigurationUtils.consumableInternal(this, getName(), "namedToIntermediaryMappings", config -> { + config.getOutgoing().artifact(namedToIntermediary.flatMap(MappingsWriter::getOutputMappings), artifact -> { + artifact.builtBy(namedToIntermediary); + }); + })); + map.put("injected-interfaces", ConfigurationUtils.consumableInternal(this, getName(), "sharedInjectedInterfacesElements", config -> { + injectedInterfacesElements.configure(injectedInterfacesElements -> { + config.getOutgoing().getArtifacts().addAllLater(project.provider(() -> injectedInterfacesElements.getOutgoing().getArtifacts())); + }); + })); + return map; + } + @Override protected FabricInstallationDependencies makeDependencies(Project project) { return project.getObjects().newInstance(FabricInstallationDependencies.class, this); @@ -958,7 +496,7 @@ void forRun(Run run, RunType runType) { task.getMinecraftVersion().set(getMinecraft()); task.dependsOn(writeLog4jConfig); }); - var remapClasspathConfiguration = forRunRemapping(run, runType); + var remapClasspathConfiguration = logic.forRunRemapping(run, runType); // TODO: figure out setting java version attribute on run classpath? @@ -1003,20 +541,16 @@ void forRun(Run run, RunType runType) { attributes.attribute(CrochetProjectPlugin.CROCHET_DISTRIBUTION_ATTRIBUTE, runType.attributeName()); }); - Configuration runMinecraft = project.getConfigurations().create("crochet"+StringUtils.capitalize(run.getName())+"RunMinecraft", config -> { - config.setCanBeConsumed(false); - project.afterEvaluate(p -> { - if (run.getAvoidNeedlessDecompilation().get()) { - config.extendsFrom(minecraft); - } else { - config.extendsFrom(minecraftLineMapped); - } - }); + project.afterEvaluate(p -> { + if (run.getAvoidNeedlessDecompilation().get()) { + run.classpath.extendsFrom(minecraft); + } else { + run.classpath.extendsFrom(minecraftLineMapped); + } }); run.classpath.extendsFrom(loaderConfiguration); run.classpath.extendsFrom(project.getConfigurations().getByName(CrochetProjectPlugin.TERMINAL_CONSOLE_APPENDER_CONFIGURATION_NAME)); - run.classpath.extendsFrom(runMinecraft); run.classpath.extendsFrom(mappingsClasspath); switch (runType) { diff --git a/src/main/java/dev/lukebemish/crochet/model/FabricInstallationDependencies.java b/src/main/java/dev/lukebemish/crochet/model/FabricInstallationDependencies.java index 8677c66..78636b0 100644 --- a/src/main/java/dev/lukebemish/crochet/model/FabricInstallationDependencies.java +++ b/src/main/java/dev/lukebemish/crochet/model/FabricInstallationDependencies.java @@ -21,10 +21,10 @@ public FabricInstallationDependencies(FabricInstallation installation) { public abstract DependencyCollector getAccessWideners(); public MappingsStructure intermediary() { - var configuration = getProject().getExtensions().getByType(MappingsConfigurationCounter.class).newConfiguration(); - configuration.fromDependencyCollector(getIntermediary()); + var configurations = getProject().getExtensions().getByType(MappingsConfigurationCounter.class).newConfiguration(); + configurations.dependencies().fromDependencyCollector(getIntermediary()); var source = getObjectFactory().newInstance(FileMappingsStructure.class); - source.getMappingsFile().from(configuration); + source.getMappingsFile().from(configurations.classpath()); return source; } } diff --git a/src/main/java/dev/lukebemish/crochet/model/FabricInstallationLogic.java b/src/main/java/dev/lukebemish/crochet/model/FabricInstallationLogic.java new file mode 100644 index 0000000..f83487c --- /dev/null +++ b/src/main/java/dev/lukebemish/crochet/model/FabricInstallationLogic.java @@ -0,0 +1,999 @@ +package dev.lukebemish.crochet.model; + +import com.google.common.base.Suppliers; +import dev.lukebemish.crochet.internal.ConfigurationUtils; +import dev.lukebemish.crochet.internal.CrochetProjectPlugin; +import dev.lukebemish.crochet.internal.FeatureUtils; +import dev.lukebemish.crochet.internal.IdeaModelHandlerPlugin; +import dev.lukebemish.crochet.internal.InheritanceMarker; +import dev.lukebemish.crochet.internal.TaskUtils; +import dev.lukebemish.crochet.internal.tasks.ArtifactTarget; +import dev.lukebemish.crochet.internal.tasks.RemapModsConfigMaker; +import dev.lukebemish.crochet.internal.tasks.RemapModsSourcesConfigMaker; +import dev.lukebemish.crochet.internal.tasks.TaskGraphExecution; +import org.apache.commons.lang3.StringUtils; +import org.gradle.api.Action; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ConsumableConfiguration; +import org.gradle.api.artifacts.DependencyScopeConfiguration; +import org.gradle.api.artifacts.ModuleDependency; +import org.gradle.api.artifacts.ProjectDependency; +import org.gradle.api.artifacts.ResolvableConfiguration; +import org.gradle.api.artifacts.type.ArtifactTypeDefinition; +import org.gradle.api.attributes.AttributeContainer; +import org.gradle.api.attributes.Bundling; +import org.gradle.api.attributes.Category; +import org.gradle.api.attributes.LibraryElements; +import org.gradle.api.attributes.Usage; +import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.file.Directory; +import org.gradle.api.file.FileCollection; +import org.gradle.api.file.RegularFile; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.SourceSetContainer; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.bundling.AbstractArchiveTask; +import org.gradle.api.tasks.bundling.Jar; +import org.jspecify.annotations.Nullable; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +import static dev.lukebemish.crochet.internal.ConfigurationUtils.copyAttributes; + +abstract class FabricInstallationLogic { + private final MinecraftInstallation minecraftInstallation; + private final Project project; + + FabricInstallationLogic(MinecraftInstallation minecraftInstallation, Project project) { + this.minecraftInstallation = minecraftInstallation; + this.project = project; + } + + protected abstract DependencyScopeConfiguration loaderConfiguration(); + protected abstract DependencyScopeConfiguration intermediaryMinecraft(); + + protected abstract void extractFabricForDependencies( + ResolvableConfiguration modCompileClasspath, + ResolvableConfiguration modRuntimeClasspath + ); + + protected abstract Provider namedToIntermediary(); + protected abstract Provider intermediaryToNamed(); + protected abstract FileCollection namedToIntermediaryFlat(); + protected abstract FileCollection intermediaryToNamedFlat(); + + protected abstract @Nullable Configuration compileExclude(); + protected abstract @Nullable Configuration runtimeExclude(); + protected abstract @Nullable Configuration compileRemapped(); + protected abstract @Nullable Configuration runtimeRemapped(); + + protected abstract void includeInterfaceInjections(ConfigurableFileCollection interfaceInjectionFiles); + + protected abstract Provider workingDirectory(); + + private static final List MOD_CONFIGURATION_NAMES = List.of( + "compileOnly", + "compileOnlyApi", + "runtimeOnly", + "implementation", + "api", + "localRuntime", + "localImplementation" + ); + + record OutConfigurations(ResolvableConfiguration modCompileClasspath, ResolvableConfiguration modRuntimeClasspath) {} + + private String getTaskName(String name, @Nullable String prefix, @Nullable String suffix) { + return StringUtils.uncapitalize((prefix == null ? "" : StringUtils.capitalize(prefix)) + StringUtils.capitalize(name) + (suffix == null ? "" : StringUtils.capitalize(suffix))); + } + + private boolean bundled = false; + + void forNamedBundle(String name, FabricRemapDependencies dependencies, Configuration remappedCompileClasspath, ConsumableConfiguration excludeCompile, Configuration remappedRuntimeClasspath, ConsumableConfiguration excludeRuntime) { + if (bundled) { + throw new IllegalStateException("Bundle already made for installation "+minecraftInstallation.getName()+" in "+project.getPath()); + } else { + bundled = true; + } + /* + General architecture: + - modCompileClasspath and modRuntimeClasspath -- collect dependencies for remapping. Resolve remap type "to-remap" + - remappedCompileClasspath and remappedRuntimeClasspath -- output of remapping + + To handle exclusions, we make another classpath: + - excludedCompileClasspath -- compileClasspath with only project/module dependencies + - excludedRuntimeClasspath -- compileClasspath with only project/module dependencies + + And finally, to handle versioning right, we make a version-determining classpath: + - versioningCompileClasspath + - versioningRuntimeClasspath + And have every resolving configuration, compileClasspath and modCompileClasspath, be shouldResolveConsistentlyWith it + */ + + Action compileAttributes = attributes -> { + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_API)); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR)); + attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL)); + attributes.attributeProvider(CrochetProjectPlugin.CROCHET_DISTRIBUTION_ATTRIBUTE, minecraftInstallation.getDistribution().map(dist -> dist.name().toLowerCase(Locale.ROOT))); + attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, minecraftInstallation.getDistribution().map(InstallationDistribution::neoAttributeValue)); + }; + + Action runtimeAttributes = attributes -> { + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR)); + attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL)); + attributes.attributeProvider(CrochetProjectPlugin.CROCHET_DISTRIBUTION_ATTRIBUTE, minecraftInstallation.getDistribution().map(dist -> dist.name().toLowerCase(Locale.ROOT))); + attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, minecraftInstallation.getDistribution().map(InstallationDistribution::neoAttributeValue)); + }; + + var modCompileClasspath = ConfigurationUtils.resolvableInternal(project, name, "modCompileClasspath", config -> { + config.attributes(attributes -> { + compileAttributes.execute(attributes); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + var modRuntimeClasspath = ConfigurationUtils.resolvableInternal(project, name, "modRuntimeClasspath", config -> { + config.attributes(attributes -> { + runtimeAttributes.execute(attributes); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + + var versioningCompileClasspath = ConfigurationUtils.resolvableInternal(project, name, "versioningCompileClasspath", config -> { + config.attributes(attributes -> { + // Does not have the remap type attribute at this point + compileAttributes.execute(attributes); + }); + config.shouldResolveConsistentlyWith(switch (minecraftInstallation.getDistribution().get()) { + case CLIENT, JOINED -> minecraftInstallation.nonUpgradableClientCompileVersioning; + case SERVER, COMMON -> minecraftInstallation.nonUpgradableServerCompileVersioning; + }); + }); + + var versioningRuntimeClasspath = ConfigurationUtils.resolvableInternal(project, name, "versioningRuntimeClasspath", config -> { + config.attributes(attributes -> { + // Does not have the remap type attribute at this point + runtimeAttributes.execute(attributes); + }); + config.shouldResolveConsistentlyWith(switch (minecraftInstallation.getDistribution().get()) { + case CLIENT, JOINED -> minecraftInstallation.nonUpgradableClientRuntimeVersioning; + case SERVER, COMMON -> minecraftInstallation.nonUpgradableServerRuntimeVersioning; + }); + }); + + var excludedCompileClasspath = ConfigurationUtils.resolvableInternal(project, name, "excludedCompileClasspath", config -> { + config.attributes(attributes -> { + compileAttributes.execute(attributes); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + }); + + var excludedRuntimeClasspath = ConfigurationUtils.resolvableInternal(project, name, "excludedRuntimeClasspath", config -> { + config.attributes(attributes -> { + runtimeAttributes.execute(attributes); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + }); + + excludedCompileClasspath.attributes(attributes -> { + compileAttributes.execute(attributes); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + + excludedRuntimeClasspath.attributes(attributes -> { + runtimeAttributes.execute(attributes); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + + excludedCompileClasspath.extendsFrom(loaderConfiguration()); + excludedCompileClasspath.extendsFrom(minecraftInstallation.minecraftDependencies); + excludedRuntimeClasspath.extendsFrom(loaderConfiguration()); + excludedRuntimeClasspath.extendsFrom(minecraftInstallation.minecraftDependencies); + + versioningCompileClasspath.extendsFrom(modCompileClasspath); + versioningCompileClasspath.extendsFrom(loaderConfiguration()); + versioningCompileClasspath.extendsFrom(minecraftInstallation.minecraftDependencies); + versioningRuntimeClasspath.extendsFrom(modRuntimeClasspath); + versioningRuntimeClasspath.extendsFrom(loaderConfiguration()); + versioningRuntimeClasspath.extendsFrom(minecraftInstallation.minecraftDependencies); + + modCompileClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); + excludedCompileClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); + + modRuntimeClasspath.shouldResolveConsistentlyWith(versioningRuntimeClasspath); + excludedRuntimeClasspath.shouldResolveConsistentlyWith(versioningRuntimeClasspath); + + excludeRuntime.extendsFrom(modRuntimeClasspath); + + excludeCompile.extendsFrom(modCompileClasspath); + + extractFabricForDependencies(modCompileClasspath, modRuntimeClasspath); + + var modCompileOnly = ConfigurationUtils.dependencyScope(project, name, "mod", "compileOnly", c -> {}); + modCompileOnly.fromDependencyCollector(dependencies.getModCompileOnly()); + modCompileClasspath.extendsFrom(modCompileOnly); + var modCompileOnlyApi = ConfigurationUtils.dependencyScope(project, name, "mod", "compileOnlyApi", c -> {}); + modCompileOnlyApi.fromDependencyCollector(dependencies.getModCompileOnlyApi()); + modCompileClasspath.extendsFrom(modCompileOnlyApi); + var modRuntimeOnly = ConfigurationUtils.dependencyScope(project, name, "mod", "runtimeOnly", c -> {}); + modRuntimeOnly.fromDependencyCollector(dependencies.getModRuntimeOnly()); + modRuntimeClasspath.extendsFrom(modRuntimeOnly); + var modLocalRuntime = ConfigurationUtils.dependencyScope(project, name, "mod", "localRuntime", c -> {}); + modLocalRuntime.fromDependencyCollector(dependencies.getModLocalRuntime()); + modRuntimeClasspath.extendsFrom(modLocalRuntime); + var modLocalImplementation = ConfigurationUtils.dependencyScope(project, name, "mod", "localImplementation", c -> {}); + modLocalImplementation.fromDependencyCollector(dependencies.getModLocalImplementation()); + modCompileClasspath.extendsFrom(modLocalImplementation); + modRuntimeClasspath.extendsFrom(modLocalImplementation); + var modImplementation = ConfigurationUtils.dependencyScope(project, name, "mod", "implementation", c -> {}); + modImplementation.fromDependencyCollector(dependencies.getModImplementation()); + modCompileClasspath.extendsFrom(modImplementation); + modRuntimeClasspath.extendsFrom(modImplementation); + var modApi = ConfigurationUtils.dependencyScope(project, name, "mod", "api", c -> {}); + modApi.fromDependencyCollector(dependencies.getModApi()); + modCompileClasspath.extendsFrom(modApi); + modRuntimeClasspath.extendsFrom(modApi); + + // Link up inheritance via CrochetFeatureContexts for the injected configurations + + var compileRemappingClasspath = ConfigurationUtils.resolvableInternal(project, name, "remappingCompileClasspath", config -> { + config.attributes(attributes -> { + compileAttributes.execute(attributes); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + + var runtimeRemappingClasspath = ConfigurationUtils.resolvableInternal(project, name, "remappingRuntimeClasspath", config -> { + config.attributes(attributes -> { + runtimeAttributes.execute(attributes); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + + compileRemappingClasspath.extendsFrom(modCompileClasspath); + compileRemappingClasspath.extendsFrom(intermediaryMinecraft()); + compileRemappingClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); + + runtimeRemappingClasspath.extendsFrom(modRuntimeClasspath); + runtimeRemappingClasspath.extendsFrom(intermediaryMinecraft()); + runtimeRemappingClasspath.shouldResolveConsistentlyWith(versioningRuntimeClasspath); + + var remappedCompileMods = project.files(); + project.getDependencies().add(remappedCompileClasspath.getName(), remappedCompileMods); + + var remappedRuntimeMods = project.files(); + project.getDependencies().add(remappedRuntimeClasspath.getName(), remappedRuntimeMods); + + var remapCompileMods = TaskUtils.registerInternal(project, TaskGraphExecution.class, name, "remapCompileClasspath", task -> { + var configMaker = project.getObjects().newInstance(RemapModsConfigMaker.class); + configMaker.setup(task, modCompileClasspath, excludedCompileClasspath, workingDirectory().get().dir("compileClasspath").dir(name), remappedCompileMods); + task.dependsOn(intermediaryToNamedFlat()); + configMaker.getDistribution().set(minecraftInstallation.getDistribution()); + configMaker.getRemappingClasspath().from(compileRemappingClasspath); + configMaker.getMappings().set(intermediaryToNamed()); + task.getConfigMaker().set(configMaker); + + addArtifacts(task); + task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + }); + remappedCompileMods.builtBy(remapCompileMods); + + var remapRuntimeMods = TaskUtils.registerInternal(project, TaskGraphExecution.class, name, "remapRuntimeClasspath", task -> { + var configMaker = project.getObjects().newInstance(RemapModsConfigMaker.class); + configMaker.setup(task, modRuntimeClasspath, excludedRuntimeClasspath, workingDirectory().get().dir("runtimeClasspath").dir(name), remappedRuntimeMods); + task.dependsOn(intermediaryToNamedFlat()); + configMaker.getDistribution().set(minecraftInstallation.getDistribution()); + configMaker.getRemappingClasspath().from(runtimeRemappingClasspath); + configMaker.getMappings().set(intermediaryToNamed()); + task.getConfigMaker().set(configMaker); + + addArtifacts(task); + task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + }); + remappedRuntimeMods.builtBy(remapRuntimeMods); + + var remapCompileModSources = TaskUtils.registerInternal(project, TaskGraphExecution.class, name, "remapCompileClasspathSources", task -> { + var configMaker = project.getObjects().newInstance(RemapModsSourcesConfigMaker.class); + configMaker.setup(task, modCompileClasspath, excludedCompileClasspath, workingDirectory().get().dir("compileClasspathSources").dir(name)); + task.dependsOn(intermediaryToNamedFlat()); + configMaker.getRemappingClasspath().from(compileRemappingClasspath); + configMaker.getMappings().set(intermediaryToNamed()); + task.getConfigMaker().set(configMaker); + + addArtifacts(task); + task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + }); + + var remapRuntimeModSources = TaskUtils.registerInternal(project, TaskGraphExecution.class, name, "remapRuntimeClasspathSources", task -> { + var configMaker = project.getObjects().newInstance(RemapModsSourcesConfigMaker.class); + configMaker.setup(task, modRuntimeClasspath, excludedRuntimeClasspath, workingDirectory().get().dir("runtimeClasspathSources").dir(name)); + task.dependsOn(intermediaryToNamedFlat()); + configMaker.getRemappingClasspath().from(runtimeRemappingClasspath); + configMaker.getMappings().set(intermediaryToNamed()); + task.getConfigMaker().set(configMaker); + + addArtifacts(task); + task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + }); + + linkSources(remapCompileMods, remapCompileModSources); + linkSources(remapRuntimeMods, remapRuntimeModSources); + + minecraftInstallation.crochetExtension.generateSources.configure(task -> { + task.dependsOn(remapCompileModSources); + task.dependsOn(remapRuntimeModSources); + }); + + minecraftInstallation.crochetExtension.idePostSync.configure(task -> { + task.dependsOn(remapCompileMods); + task.dependsOn(remapRuntimeMods); + }); + } + + OutConfigurations forFeatureShared(SourceSet sourceSet, FabricRemapDependencies dependencies, boolean local) { + /* + General architecture: + - compileClasspath -- the normal classpath for the mod. Specifically selects "not-to-remap" dependencies + - modCompileClasspath and modRuntimeClasspath -- collect dependencies for remapping. Resolve remap type "to-remap" + - modApiElements and modRuntimeElements -- remap type "to-remap", these expose to-remap dependencies, but no artifacts, transitively + - nonModApiElements and nonModRuntimeElements -- remap type "not-ro-remap", these expose the non-remapped project artifacts/variants, as well as any non-remapped deps, transitively + - remappedCompileClasspath -- output of remapping, used to insert stuff into compile classpath + + ATs and IIs are grabbed from anything that is on both modRuntime and modCompile classpaths + TODO: see if we can limit that to only care about compile by having a separate MC jar for runtime + + Remapping is set up here only for the compile classpath. The excluded deps should be anything on compileClasspath that _isn't_ the remapped dependencies (or file dependencies) + To handle exclusions, we make another classpath: + - excludedCompileClasspath -- compileClasspath with only project/module dependencies + - this should also be given everything on the relevant non-upgradable classpath + + And finally, to handle versioning right, we make a version-determining classpath: + - versioningCompileClasspath + And have every resolving configuration, compileClasspath and modCompileClasspath, be shouldResolveConsistentlyWith it + + If this is non-local + - remapJar, remapSourcesJar -- remapped jar dependencies + */ + + project.getConfigurations().named(sourceSet.getTaskName(null, JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME), config -> { + config.extendsFrom(loaderConfiguration()); + }); + + var modCompileClasspath = ConfigurationUtils.resolvableInternal(project, sourceSet.getName(), "modCompileClasspath", config -> { + config.attributes(attributes -> { + copyAttributes(project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + var modRuntimeClasspath = ConfigurationUtils.resolvableInternal(project, sourceSet.getName(), "modRuntimeClasspath", config -> { + config.attributes(attributes -> { + copyAttributes(project.getConfigurations().getByName(sourceSet.getRuntimeClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + + + var versioningCompileDependencies = ConfigurationUtils.dependencyScopeInternal(project, sourceSet.getName(), "versioningCompileDependencies", config -> {}); + var versioningCompileClasspath = ConfigurationUtils.resolvableInternal(project, sourceSet.getName(), "versioningCompileClasspath", config -> { + config.attributes(attributes -> { + // Does not have the remap type attribute at this point + copyAttributes(project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); + }); + config.extendsFrom(versioningCompileDependencies); + config.shouldResolveConsistentlyWith(switch (minecraftInstallation.getDistribution().get()) { + case CLIENT, JOINED -> minecraftInstallation.nonUpgradableClientCompileVersioning; + case SERVER, COMMON -> minecraftInstallation.nonUpgradableServerCompileVersioning; + }); + }); + + var excludedCompileDependencies = ConfigurationUtils.dependencyScopeInternal(project, sourceSet.getName(), "excludedCompileDependencies", config -> {}); + var excludedCompileClasspath = ConfigurationUtils.resolvableInternal(project, sourceSet.getName(), "excludedCompileClasspath", config -> { + config.attributes(attributes -> { + copyAttributes(project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + config.extendsFrom(excludedCompileDependencies); + }); + + if (compileExclude() != null) { + modCompileClasspath.extendsFrom(compileExclude()); + } + + var compileClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()); + excludedCompileClasspath.getDependencyConstraints().addAllLater(project.provider(compileClasspath::getDependencyConstraints)); + // Only non-file-collection dependencies + excludedCompileClasspath.getDependencies().addAllLater(project.provider(compileClasspath::getAllDependencies).map(deps -> new ArrayList<>(deps.stream().filter(dep -> (dep instanceof ModuleDependency)).toList()))); + if (compileExclude() != null) { + excludedCompileClasspath.extendsFrom(compileExclude()); + } + + versioningCompileClasspath.extendsFrom(modCompileClasspath); + versioningCompileClasspath.extendsFrom(compileClasspath); + if (compileExclude() != null) { + versioningCompileClasspath.extendsFrom(compileExclude()); + } + compileClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); + modCompileClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); + excludedCompileClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); + + extractFabricForDependencies(modCompileClasspath, modRuntimeClasspath); + + class ConfigurationExtension implements Consumer { + private final List configurations = new ArrayList<>(); + private @Nullable Consumer next = null; + + @Override + public synchronized void accept(Configuration configuration) { + if (next == null) { + configurations.add(configuration); + } else { + next.accept(configuration); + } + } + + public void setNext(Consumer next) { + this.next = next; + configurations.forEach(next); + configurations.clear(); + } + } + + var modApiElementsExtendsFrom = new ConfigurationExtension(); + var modRuntimeElementsExtendsFrom = new ConfigurationExtension(); + var apiElementsExtendsFrom = new ConfigurationExtension(); + var runtimeElementsExtendsFrom = new ConfigurationExtension(); + + FeatureUtils.forSourceSetFeature(project, sourceSet.getName(), context -> { + var runtimeElements = context.getRuntimeElements(); + runtimeElementsExtendsFrom.setNext(runtimeElements::extendsFrom); + var apiElements = context.getApiElements(); + apiElementsExtendsFrom.setNext(apiElements::extendsFrom); + + var modRuntimeElements = ConfigurationUtils.consumableInternal(project, sourceSet.getName(), "modRuntimeElements", config -> { + config.attributes(attributes -> { + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + copyAttributes(runtimeElements.getAttributes(), attributes, project.getProviders()); + }); + }); + modRuntimeElementsExtendsFrom.setNext(modRuntimeElements::extendsFrom); + var modApiElements = ConfigurationUtils.consumableInternal(project, sourceSet.getName(), "modApiElements", config -> { + config.attributes(attributes -> { + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + copyAttributes(apiElements.getAttributes(), attributes, project.getProviders()); + }); + }); + modApiElementsExtendsFrom.setNext(modApiElements::extendsFrom); + var nonModRuntimeElements = ConfigurationUtils.consumableInternal(project, sourceSet.getName(), "nonModRuntimeElements", config -> { + config.attributes(attributes -> { + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + copyAttributes(runtimeElements.getAttributes(), attributes, project.getProviders()); + }); + }); + var nonModApiElements = ConfigurationUtils.consumableInternal(project, sourceSet.getName(), "nonModApiElements", config -> { + config.attributes(attributes -> { + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + copyAttributes(apiElements.getAttributes(), attributes, project.getProviders()); + }); + }); + + context.withCapabilities(modRuntimeElements); + context.withCapabilities(modApiElements); + context.withCapabilities(nonModRuntimeElements); + context.withCapabilities(nonModApiElements); + + nonModApiElements.getDependencyConstraints().addAllLater(project.provider(apiElements::getDependencyConstraints)); + nonModRuntimeElements.getDependencyConstraints().addAllLater(project.provider(runtimeElements::getDependencyConstraints)); + + nonModApiElements.getDependencies().addAllLater(project.provider(() -> + apiElements.getAllDependencies().stream().filter(dep -> (dep instanceof ProjectDependency) || !modCompileClasspath.getAllDependencies().contains(dep)).toList() + )); + nonModRuntimeElements.getDependencies().addAllLater(project.provider(() -> + runtimeElements.getAllDependencies().stream().filter(dep -> (dep instanceof ProjectDependency) || !modRuntimeClasspath.getAllDependencies().contains(dep)).toList() + )); + + // An empty configuration, so that the RemapModsSourcesConfigMaker will skip the actual sources jar + var remappedSourcesElements = Suppliers.memoize(() -> + ConfigurationUtils.consumableInternal(project, sourceSet.getName(), "remappedSourcesElements", config -> { + var sourcesElements = project.getConfigurations().getByName(sourceSet.getSourcesElementsConfigurationName()); + config.attributes(attributes -> { + copyAttributes(sourcesElements.getAttributes(), attributes, project.getProviders()); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + }); + }) + ); + + if (!local) { + { + // Remap jar + var jarTask = project.getTasks().named(sourceSet.getJarTaskName(), Jar.class); + + AtomicBoolean hasSetup = new AtomicBoolean(false); + AtomicReference classifier = new AtomicReference<>(); + AtomicReference oldJarLocation = new AtomicReference<>(); + + BiConsumer configurator = (remapJar, jar) -> { + if (hasSetup.getAndSet(true)) { + return; + } + var configMaker = project.getObjects().newInstance(RemapModsConfigMaker.class); + var oldArchiveFile = jarTask.get().getArchiveFile().get(); + var existingClassifier = jarTask.get().getArchiveClassifier().get(); + + classifier.set(existingClassifier); + oldJarLocation.set(oldArchiveFile); + + jarTask.get().getArchiveClassifier().set(existingClassifier.isEmpty() ? "dev" : existingClassifier + "-dev"); + var remappingClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getIncoming().artifactView(view -> view.attributes(attributes -> + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE) + )).getFiles(); + configMaker.remapSingleJar(remapJar, input -> { + input.set(jarTask.flatMap(AbstractArchiveTask::getArchiveFile)); + }, output -> { + output.set(oldArchiveFile); + }, mappings -> { + }, remappingClasspath); + // Change direction and whatnot + configMaker.getIsReObf().set(true); + + configMaker.getStripNestedJars().set(false); + includeInterfaceInjections(configMaker.getIncludedInterfaceInjections()); + + configMaker.getMappings().set(namedToIntermediary()); + remapJar.dependsOn(namedToIntermediaryFlat()); + remapJar.getConfigMaker().set(configMaker); + + remapJar.artifactsConfiguration(project.getConfigurations().getByName(CrochetProjectPlugin.TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME)); + remapJar.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + remapJar.dependsOn(jarTask); + + // Set up mixin remapping flags via jar manifest + jar.manifest(m -> m.attributes(Map.of( + "Fabric-Loom-Mixin-Remap-Type", "STATIC" + ))); + }; + + var remapJarTask = project.getTasks().register(sourceSet.getTaskName("remap", "jar"), TaskGraphExecution.class, task -> { + configurator.accept(task, jarTask.get()); + }); + + jarTask.configure(task -> { + configurator.accept(remapJarTask.get(), task); + }); + + project.getTasks().named("build", t -> t.dependsOn(remapJarTask)); + + nonModRuntimeElements.getOutgoing().getArtifacts().addAll(runtimeElements.getAllArtifacts()); + runtimeElements.getOutgoing().getArtifacts().clear(); + nonModApiElements.getOutgoing().getArtifacts().addAll(apiElements.getAllArtifacts()); + apiElements.getOutgoing().getArtifacts().clear(); + + project.artifacts(artifacts -> { + configurator.accept(remapJarTask.get(), jarTask.get()); + artifacts.add(runtimeElements.getName(), oldJarLocation.get(), spec -> { + spec.setClassifier(classifier.get()); + spec.setType(ArtifactTypeDefinition.JAR_TYPE); + spec.builtBy(remapJarTask); + }); + artifacts.add(apiElements.getName(), oldJarLocation.get(), spec -> { + spec.setClassifier(classifier.get()); + spec.setType(ArtifactTypeDefinition.JAR_TYPE); + spec.builtBy(remapJarTask); + }); + }); + } + + { + // Remap sources jar + AtomicBoolean hasSetup = new AtomicBoolean(false); + AtomicReference classifier = new AtomicReference<>(); + AtomicReference oldJarLocation = new AtomicReference<>(); + + BiConsumer configurator = (remapSourcesJar, sourcesJar) -> { + if (hasSetup.getAndSet(true)) { + return; + } + var configMaker = project.getObjects().newInstance(RemapModsSourcesConfigMaker.class); + var oldArchiveFile = sourcesJar.getArchiveFile().get(); + var existingClassifier = sourcesJar.getArchiveClassifier().get(); + + classifier.set(existingClassifier); + oldJarLocation.set(oldArchiveFile); + + sourcesJar.getArchiveClassifier().set(existingClassifier.isEmpty() ? "dev" : existingClassifier + "-dev"); + var remappingClasspath = project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getIncoming().artifactView(view -> view.attributes(attributes -> + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE) + )).getFiles(); + configMaker.remapSingleJar(remapSourcesJar, input -> { + input.set(sourcesJar.getArchiveFile()); + }, output -> { + output.set(oldArchiveFile); + }, mappings -> { + }, remappingClasspath); + + configMaker.getMappings().set(namedToIntermediary()); + remapSourcesJar.dependsOn(namedToIntermediaryFlat()); + remapSourcesJar.getConfigMaker().set(configMaker); + + remapSourcesJar.artifactsConfiguration(project.getConfigurations().getByName(CrochetProjectPlugin.TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME)); + remapSourcesJar.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + remapSourcesJar.dependsOn(sourcesJar); + }; + + AtomicBoolean registered = new AtomicBoolean(false); + + var remapTaskName = sourceSet.getTaskName("remap", "SourcesJar"); + + Consumer maybeConfigure = jarTask -> { + if (registered.compareAndSet(false, true)) { + var remapTask = project.getTasks().register(remapTaskName, TaskGraphExecution.class); + configurator.accept(remapTask.get(), jarTask); + project.getTasks().named("build", t -> t.dependsOn(remapTask)); + + var sourcesElements = project.getConfigurations().getByName(sourceSet.getSourcesElementsConfigurationName()); + sourcesElements.getOutgoing().getArtifacts().clear(); + + remappedSourcesElements.get(); + + project.artifacts(artifacts -> { + artifacts.add(sourcesElements.getName(), oldJarLocation.get(), spec -> { + spec.setClassifier(classifier.get()); + spec.setType(ArtifactTypeDefinition.JAR_TYPE); + spec.builtBy(remapTask); + }); + }); + } + }; + + project.getTasks().withType(Jar.class, sourceJarTask -> { + if (sourceJarTask.getName().equals(sourceSet.getSourcesJarTaskName())) { + maybeConfigure.accept(sourceJarTask); + } + }); + + project.afterEvaluate(p -> { + var sourceJarTask = project.getTasks().findByName(sourceSet.getSourcesJarTaskName()); + if (sourceJarTask instanceof Jar jarTask) { + maybeConfigure.accept(jarTask); + } + }); + } + } else { + nonModApiElements.getOutgoing().getArtifacts().addAllLater(project.provider(apiElements::getAllArtifacts)); + nonModRuntimeElements.getOutgoing().getArtifacts().addAllLater(project.provider(runtimeElements::getAllArtifacts)); + } + apiElements.getOutgoing().getVariants().forEach(variant -> { + var newVariant = nonModApiElements.getOutgoing().getVariants().create(variant.getName()); + copyAttributes(variant.getAttributes(), newVariant.getAttributes(), project.getProviders()); + newVariant.getArtifacts().addAllLater(project.provider(variant::getArtifacts)); + newVariant.getAttributes().attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, dev.lukebemish.crochet.internal.CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + apiElements.getOutgoing().getVariants().clear(); + runtimeElements.getOutgoing().getVariants().forEach(variant -> { + var newVariant = nonModRuntimeElements.getOutgoing().getVariants().create(variant.getName()); + copyAttributes(variant.getAttributes(), newVariant.getAttributes(), project.getProviders()); + newVariant.getArtifacts().addAllLater(project.provider(variant::getArtifacts)); + newVariant.getAttributes().attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + runtimeElements.getOutgoing().getVariants().clear(); + }); + + var modCompileOnly = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), "mod", "compileOnly", c -> {}); + modCompileOnly.fromDependencyCollector(dependencies.getModCompileOnly()); + modCompileClasspath.extendsFrom(modCompileOnly); + var modCompileOnlyApi = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), "mod", "compileOnlyApi", c -> {}); + modCompileOnlyApi.fromDependencyCollector(dependencies.getModCompileOnlyApi()); + modCompileClasspath.extendsFrom(modCompileOnlyApi); + apiElementsExtendsFrom.accept(modCompileOnlyApi); + modApiElementsExtendsFrom.accept(modCompileOnlyApi); + var modRuntimeOnly = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), "mod", "runtimeOnly", c -> {}); + modRuntimeOnly.fromDependencyCollector(dependencies.getModRuntimeOnly()); + modRuntimeClasspath.extendsFrom(modRuntimeOnly); + runtimeElementsExtendsFrom.accept(modRuntimeOnly); + modRuntimeElementsExtendsFrom.accept(modRuntimeOnly); + var modLocalRuntime = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), "mod", "localRuntime", c -> {}); + modLocalRuntime.fromDependencyCollector(dependencies.getModLocalRuntime()); + modRuntimeClasspath.extendsFrom(modLocalRuntime); + var modLocalImplementation = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), "mod", "localImplementation", c -> {}); + modLocalImplementation.fromDependencyCollector(dependencies.getModLocalImplementation()); + modCompileClasspath.extendsFrom(modLocalImplementation); + modRuntimeClasspath.extendsFrom(modLocalImplementation); + var modImplementation = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), "mod", "implementation", c -> {}); + modImplementation.fromDependencyCollector(dependencies.getModImplementation()); + modCompileClasspath.extendsFrom(modImplementation); + modRuntimeClasspath.extendsFrom(modImplementation); + runtimeElementsExtendsFrom.accept(modImplementation); + modRuntimeElementsExtendsFrom.accept(modImplementation); + var modApi = ConfigurationUtils.dependencyScope(project, sourceSet.getName(), "mod", "api", c -> {}); + modApi.fromDependencyCollector(dependencies.getModApi()); + modCompileClasspath.extendsFrom(modApi); + modRuntimeClasspath.extendsFrom(modApi); + apiElementsExtendsFrom.accept(modApi); + modApiElementsExtendsFrom.accept(modApi); + runtimeElementsExtendsFrom.accept(modApi); + modRuntimeElementsExtendsFrom.accept(modApi); + + // Link up inheritance via CrochetFeatureContexts for the injected configurations + var marker = InheritanceMarker.getOrCreate(project.getObjects(), sourceSet); + marker.getShouldTakeConfigurationsFrom().configureEach(name -> { + var otherSourceSet = project.getExtensions().getByType(SourceSetContainer.class).findByName(name); + var otherInstallation = minecraftInstallation.crochetExtension.findInstallation(otherSourceSet); + if (otherInstallation instanceof FabricInstallation) { + for (var confName : MOD_CONFIGURATION_NAMES) { + var thisConf = project.getConfigurations().getByName(sourceSet.getTaskName("mod", confName)); + var otherConf = project.getConfigurations().getByName(otherSourceSet.getTaskName("mod", confName)); + thisConf.extendsFrom(otherConf); + } + } + }); + marker.getShouldGiveConfigurationsTo().configureEach(name -> { + var otherSourceSet = project.getExtensions().getByType(SourceSetContainer.class).findByName(name); + var otherInstallation = minecraftInstallation.crochetExtension.findInstallation(otherSourceSet); + if (otherInstallation instanceof FabricInstallation) { + for (var confName : MOD_CONFIGURATION_NAMES) { + var thisConf = project.getConfigurations().getByName(sourceSet.getTaskName("mod", confName)); + var otherConf = project.getConfigurations().getByName(otherSourceSet.getTaskName("mod", confName)); + otherConf.extendsFrom(thisConf); + } + } + }); + + var remappedCompileClasspath = ConfigurationUtils.dependencyScopeInternal(project, sourceSet.getName(), "remappedCompileClasspath", c -> { + if (compileRemapped() != null) { + c.extendsFrom(compileRemapped()); + } + }); + project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).extendsFrom(remappedCompileClasspath); + + var compileRemappingClasspath = ConfigurationUtils.resolvableInternal(project, sourceSet.getName(), "remappingCompileClasspath", config -> { + config.attributes(attributes -> { + copyAttributes(project.getConfigurations().getByName(sourceSet.getCompileClasspathConfigurationName()).getAttributes(), attributes, project.getProviders()); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + + project.getConfigurations().named(sourceSet.getCompileClasspathConfigurationName(), config -> { + config.attributes(attributes -> + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP) + ); + config.getDependencies().addAllLater(project.provider(() -> + modCompileClasspath.getAllDependencies().stream().filter(dep -> dep instanceof ProjectDependency).toList() + )); + }); + + compileRemappingClasspath.extendsFrom(modCompileClasspath); + compileRemappingClasspath.extendsFrom(intermediaryMinecraft()); + compileRemappingClasspath.shouldResolveConsistentlyWith(versioningCompileClasspath); + + var remappedCompileMods = project.files(); + project.getDependencies().add(remappedCompileClasspath.getName(), remappedCompileMods); + + var remapCompileMods = TaskUtils.registerInternal(project, TaskGraphExecution.class, sourceSet.getName(), "remapCompileClasspath", task -> { + var configMaker = project.getObjects().newInstance(RemapModsConfigMaker.class); + configMaker.setup(task, modCompileClasspath, excludedCompileClasspath, workingDirectory().get().dir("compileClasspath").dir(sourceSet.getName()), remappedCompileMods); + task.dependsOn(intermediaryToNamedFlat()); + configMaker.getDistribution().set(minecraftInstallation.getDistribution()); + configMaker.getRemappingClasspath().from(compileRemappingClasspath); + configMaker.getMappings().set(intermediaryToNamed()); + task.getConfigMaker().set(configMaker); + + addArtifacts(task); + task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + }); + remappedCompileMods.builtBy(remapCompileMods); + + var remapCompileModSources = TaskUtils.registerInternal(project, TaskGraphExecution.class, sourceSet.getName(), "remapCompileClasspathSources", task -> { + var configMaker = project.getObjects().newInstance(RemapModsSourcesConfigMaker.class); + configMaker.setup(task, modCompileClasspath, excludedCompileClasspath, workingDirectory().get().dir("compileClasspathSources").dir(sourceSet.getName())); + task.dependsOn(intermediaryToNamedFlat()); + configMaker.getRemappingClasspath().from(compileRemappingClasspath); + configMaker.getMappings().set(intermediaryToNamed()); + task.getConfigMaker().set(configMaker); + + addArtifacts(task); + task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + }); + + linkSources(remapCompileMods, remapCompileModSources); + + minecraftInstallation.crochetExtension.idePostSync.configure(task -> { + task.dependsOn(remapCompileMods); + }); + minecraftInstallation.crochetExtension.generateSources.configure(task -> { + task.dependsOn(remapCompileModSources); + }); + return new OutConfigurations(modCompileClasspath, modRuntimeClasspath); + } + + protected abstract void addArtifacts(TaskGraphExecution task); + + void linkSources(TaskProvider remapJars, TaskProvider remapSourceJars) { + record Pair(T first, U second) implements Serializable {} + + if (IdeaModelHandlerPlugin.isIdeaSyncRelated(project)) { + Provider> sources = remapSourceJars.get().getConfigMaker().map(configMaker -> { + var targets = ((RemapModsSourcesConfigMaker) configMaker).getTargets().get(); + Map map = new HashMap<>(); + targets.forEach(target -> target.getCapabilities().get().forEach(cap -> map.put(cap, target))); + return map; + }); + + Provider>> binariesToSources = sources.zip(remapJars.get().getConfigMaker().flatMap(configMaker -> ((RemapModsConfigMaker) configMaker).getTargets()), (sourceMap, binaryTargets) -> { + List> map = new ArrayList<>(); + binaryTargets.forEach(binary -> { + Set sourceTargets = new HashSet<>(); + binary.getCapabilities().get().forEach(cap -> { + ArtifactTarget source = sourceMap.get(cap); + if (source != null) { + sourceTargets.add(source); + } + }); + if (sourceTargets.size() == 1) { + map.add(new Pair<>(binary, sourceTargets.iterator().next())); + } + }); + return map; + }); + + IdeaModelHandlerPlugin.retrieve(project).mapBinariesToSources( + binariesToSources.map(pairs -> pairs.stream().map(p -> p.first().getTarget().get()).toList()), + binariesToSources.map(pairs -> pairs.stream().map(p -> p.second().getTarget().get()).toList()) + ); + } + } + + Configuration forRunRemapping(Run run, RunType runType) { + /* + General architecture + - run.classpath -- the normal run classpath. We make it select "not-to-remap" dependencies + - modClasspath -- collect dependencies for remapping. Resolve remap type "to-remap" + - remappedClasspath -- output of remapping, used to insert stuff into run classpath + + To exclude deps, we make another classpath: + - excludedClasspath -- run.classpath with only project/module dependencies + + And we still need to pin versions: + - versioningClasspath + And have every resolving configuration, run.classpath and modClasspath, be shouldResolveConsistentlyWith it + */ + + var implementation = ConfigurationUtils.dependencyScopeInternal(project, run.getName(), "runImplementation", config -> { + config.fromDependencyCollector(run.getImplementation()); + }); + + var runClasspath = run.classpath; + var modClasspath = ConfigurationUtils.resolvableInternal(project, run.getName(), "runModClasspath", config -> { + config.attributes(attributes -> { + copyAttributes(runClasspath.getAttributes(), attributes, project.getProviders()); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + if (runtimeExclude() != null) { + modClasspath.extendsFrom(runtimeExclude()); + } + modClasspath.extendsFrom(implementation); + runClasspath.extendsFrom(implementation); + + var remappedClasspath = ConfigurationUtils.dependencyScopeInternal(project, run.getName(), "remappedRunClasspath", config -> { + if (runtimeRemapped() != null) { + config.extendsFrom(runtimeRemapped()); + } + }); + + var excludedClasspath = ConfigurationUtils.resolvableInternal(project, run.getName(), "excludedRunClasspath", config -> { + config.attributes(attributes -> { + copyAttributes(runClasspath.getAttributes(), attributes, project.getProviders()); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + config.extendsFrom(implementation); + if (runtimeExclude() != null) { + config.extendsFrom(runtimeExclude()); + } + }); + + var versioningClasspath = ConfigurationUtils.resolvableInternal(project, run.getName(), "runVersioningClasspath", config -> { + config.attributes(attributes -> { + copyAttributes(runClasspath.getAttributes(), attributes, project.getProviders()); + }); + config.shouldResolveConsistentlyWith(switch (runType) { + case CLIENT -> minecraftInstallation.nonUpgradableClientRuntimeVersioning; + case SERVER -> minecraftInstallation.nonUpgradableServerRuntimeVersioning; + case DATA -> minecraftInstallation.nonUpgradableClientRuntimeVersioning; + }); + }); + + versioningClasspath.extendsFrom(modClasspath); + versioningClasspath.extendsFrom(runClasspath); + runClasspath.shouldResolveConsistentlyWith(versioningClasspath); + modClasspath.shouldResolveConsistentlyWith(versioningClasspath); + excludedClasspath.shouldResolveConsistentlyWith(versioningClasspath); + + runClasspath.attributes(attributes -> { + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_NON_REMAP); + }); + + run.classpath.extendsFrom(remappedClasspath); + + var remappingClasspath = ConfigurationUtils.resolvableInternal(project, run.getName(), "runRemappingClasspath", config -> { + config.attributes(attributes -> { + copyAttributes(runClasspath.getAttributes(), attributes, project.getProviders()); + attributes.attribute(CrochetProjectPlugin.CROCHET_REMAP_TYPE_ATTRIBUTE, CrochetProjectPlugin.CROCHET_REMAP_TYPE_REMAP); + attributes.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE); + }); + }); + remappingClasspath.extendsFrom(modClasspath); + + remappingClasspath.extendsFrom(modClasspath); + remappingClasspath.extendsFrom(intermediaryMinecraft()); + remappingClasspath.shouldResolveConsistentlyWith(versioningClasspath); + + var remappedMods = project.files(); + project.getDependencies().add(remappedClasspath.getName(), remappedMods); + + var remapMods = project.getTasks().register("crochetRemap"+StringUtils.capitalize(run.getName())+"RunClasspath", TaskGraphExecution.class, task -> { + var configMaker = project.getObjects().newInstance(RemapModsConfigMaker.class); + configMaker.setup(task, modClasspath, excludedClasspath, workingDirectory().get().dir("runClasspath").dir(run.getName()), remappedMods); + task.dependsOn(intermediaryToNamedFlat()); + configMaker.getDistribution().set(minecraftInstallation.getDistribution()); + configMaker.getRemappingClasspath().from(remappingClasspath); + configMaker.getMappings().set(intermediaryToNamed()); + task.getConfigMaker().set(configMaker); + + addArtifacts(task); + task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + }); + remappedMods.builtBy(remapMods); + + var remapModSources = project.getTasks().register("crochetRemap"+StringUtils.capitalize(run.getName())+"RunClasspathSources", TaskGraphExecution.class, task -> { + var configMaker = project.getObjects().newInstance(RemapModsSourcesConfigMaker.class); + configMaker.setup(task, modClasspath, excludedClasspath, workingDirectory().get().dir("runClasspathSources").dir(run.getName())); + task.dependsOn(intermediaryToNamedFlat()); + configMaker.getRemappingClasspath().from(remappingClasspath); + configMaker.getMappings().set(intermediaryToNamed()); + task.getConfigMaker().set(configMaker); + + addArtifacts(task); + task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); + }); + + linkSources(remapMods, remapModSources); + + minecraftInstallation.crochetExtension.idePostSync.configure(task -> { + task.dependsOn(remapMods); + + task.dependsOn(remapModSources); + }); + + return remappingClasspath; + } +} diff --git a/src/main/java/dev/lukebemish/crochet/model/FabricRemapDependencies.java b/src/main/java/dev/lukebemish/crochet/model/FabricRemapDependencies.java index 92bfe5a..1abe2e5 100644 --- a/src/main/java/dev/lukebemish/crochet/model/FabricRemapDependencies.java +++ b/src/main/java/dev/lukebemish/crochet/model/FabricRemapDependencies.java @@ -4,12 +4,12 @@ import org.gradle.api.artifacts.dsl.DependencyCollector; @SuppressWarnings("UnstableApiUsage") -public interface FabricRemapDependencies extends Dependencies { - DependencyCollector getModCompileOnly(); - DependencyCollector getModCompileOnlyApi(); - DependencyCollector getModRuntimeOnly(); - DependencyCollector getModLocalRuntime(); - DependencyCollector getModLocalImplementation(); - DependencyCollector getModImplementation(); - DependencyCollector getModApi(); +public abstract class FabricRemapDependencies implements Dependencies { + public abstract DependencyCollector getModCompileOnly(); + public abstract DependencyCollector getModCompileOnlyApi(); + public abstract DependencyCollector getModRuntimeOnly(); + public abstract DependencyCollector getModLocalRuntime(); + public abstract DependencyCollector getModLocalImplementation(); + public abstract DependencyCollector getModImplementation(); + public abstract DependencyCollector getModApi(); } diff --git a/src/main/java/dev/lukebemish/crochet/model/FabricSourceSetDependencies.java b/src/main/java/dev/lukebemish/crochet/model/FabricSourceSetDependencies.java index 22cce91..d230631 100644 --- a/src/main/java/dev/lukebemish/crochet/model/FabricSourceSetDependencies.java +++ b/src/main/java/dev/lukebemish/crochet/model/FabricSourceSetDependencies.java @@ -1,3 +1,3 @@ package dev.lukebemish.crochet.model; -public abstract class FabricSourceSetDependencies implements FabricRemapDependencies {} +public abstract class FabricSourceSetDependencies extends FabricRemapDependencies {} diff --git a/src/main/java/dev/lukebemish/crochet/model/LocalMinecraftInstallation.java b/src/main/java/dev/lukebemish/crochet/model/LocalMinecraftInstallation.java index b479439..770ce47 100644 --- a/src/main/java/dev/lukebemish/crochet/model/LocalMinecraftInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/LocalMinecraftInstallation.java @@ -1,9 +1,14 @@ package dev.lukebemish.crochet.model; +import com.google.common.base.Suppliers; +import dev.lukebemish.crochet.internal.ConfigurationUtils; import dev.lukebemish.crochet.internal.CrochetProjectPlugin; import dev.lukebemish.crochet.internal.FeatureUtils; import dev.lukebemish.crochet.internal.IdeaModelHandlerPlugin; +import dev.lukebemish.crochet.internal.Memoize; +import dev.lukebemish.crochet.internal.TaskUtils; import dev.lukebemish.crochet.internal.tasks.TaskGraphExecution; +import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.DependencyScopeConfiguration; @@ -27,24 +32,25 @@ import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; public abstract class LocalMinecraftInstallation extends MinecraftInstallation { @SuppressWarnings("UnstableApiUsage") - final Provider accessTransformers; + final DependencyScopeConfiguration accessTransformers; @SuppressWarnings("UnstableApiUsage") - final Provider accessTransformersPath; + final ResolvableConfiguration accessTransformersPath; @SuppressWarnings("UnstableApiUsage") - final Provider accessTransformersApi; + final DependencyScopeConfiguration accessTransformersApi; @SuppressWarnings("UnstableApiUsage") - final Provider injectedInterfaces; + final DependencyScopeConfiguration injectedInterfaces; @SuppressWarnings("UnstableApiUsage") - final Provider injectedInterfacesPath; + final ResolvableConfiguration injectedInterfacesPath; @SuppressWarnings("UnstableApiUsage") - final Provider injectedInterfacesApi; + final DependencyScopeConfiguration injectedInterfacesApi; - final Provider accessTransformersElements; - final Provider injectedInterfacesElements; + final Supplier accessTransformersElements; + final Memoize injectedInterfacesElements; final TaskProvider downloadAssetsTask; @@ -61,15 +67,6 @@ public abstract class LocalMinecraftInstallation extends MinecraftInstallation { final TaskProvider sourcesArtifactsTask; final TaskProvider lineMappedBinaryArtifactsTask; - final Configuration minecraftElements; - final Configuration minecraftDependenciesElements; - final Configuration minecraftResourcesElements; - final Configuration minecraftLineMappedElements; - - final Configuration nonUpgradableElements; - - final Configuration assetsPropertiesElements; - @SuppressWarnings("UnstableApiUsage") @Inject public LocalMinecraftInstallation(String name, CrochetExtension extension) { @@ -78,8 +75,7 @@ public LocalMinecraftInstallation(String name, CrochetExtension extension) { var project = extension.project; this.assetsProperties = project.getLayout().getBuildDirectory().file("crochet/installations/"+name+"/assets.properties"); - this.downloadAssetsTask = project.getTasks().register(name+"CrochetDownloadAssets", TaskGraphExecution.class, task -> { - task.setGroup("crochet setup"); + this.downloadAssetsTask = TaskUtils.registerInternal(this, TaskGraphExecution.class, name, "DownloadAssets", task -> { task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); task.getTargets().add(TaskGraphExecution.GraphOutput.of("assets", assetsProperties, project.getObjects())); // Bounce, to avoid capturing the installation in the args @@ -105,52 +101,51 @@ public LocalMinecraftInstallation(String name, CrochetExtension extension) { this.dependencies = makeDependencies(project); - this.accessTransformersApi = project.getConfigurations().dependencyScope(name+"AccessTransformersApi", config -> { + this.accessTransformersApi = ConfigurationUtils.dependencyScope(this, name, null, "accessTransformersApi", config -> { config.fromDependencyCollector(getDependencies().getAccessTransformersApi()); }); - this.accessTransformers = project.getConfigurations().dependencyScope(name+"AccessTransformers", config -> { + this.accessTransformers = ConfigurationUtils.dependencyScope(this, name, null, "accessTransformers", config -> { config.fromDependencyCollector(getDependencies().getAccessTransformers()); }); - this.accessTransformersPath = project.getConfigurations().resolvable(name+"AccessTransformersPath", config -> { + this.accessTransformersPath = ConfigurationUtils.resolvableInternal(this, name, "accessTransformersPath", config -> { config.attributes(attributes -> attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, ACCESS_TRANSFORMER_CATEGORY)) ); - config.extendsFrom(this.accessTransformersApi.get()); - config.extendsFrom(this.accessTransformers.get()); + config.extendsFrom(this.accessTransformersApi); + config.extendsFrom(this.accessTransformers); }); - this.injectedInterfacesApi = project.getConfigurations().dependencyScope(name+"InterfaceInjectionsApi", config -> { + this.injectedInterfacesApi = ConfigurationUtils.dependencyScope(this, name, null, "injectedInterfacesApi", config -> { config.fromDependencyCollector(getDependencies().getInjectedInterfacesApi()); }); - this.injectedInterfaces = project.getConfigurations().dependencyScope(name+"InterfaceInjections", config -> { + this.injectedInterfaces = ConfigurationUtils.dependencyScope(this, name, null, "injectedInterfaces", config -> { config.fromDependencyCollector(getDependencies().getInjectedInterfaces()); }); - this.injectedInterfacesPath = project.getConfigurations().resolvable(name+"InterfaceInjectionsPath", config -> { + this.injectedInterfacesPath = ConfigurationUtils.resolvableInternal(this, name, "injectedInterfacesPath", config -> { config.attributes(attributes -> attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, INTERFACE_INJECTION_CATEGORY)) ); - config.extendsFrom(this.injectedInterfacesApi.get()); - config.extendsFrom(this.injectedInterfaces.get()); + config.extendsFrom(this.injectedInterfacesApi); + config.extendsFrom(this.injectedInterfaces); }); - this.accessTransformersElements = project.getConfigurations().register(name+"AccessTransformersElements", config -> { + this.accessTransformersElements = Suppliers.memoize(() -> ConfigurationUtils.consumable(this, name, null, "accessTransformersElements", config -> { config.attributes(attributes -> attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, ACCESS_TRANSFORMER_CATEGORY)) ); - config.setCanBeResolved(false); - config.setCanBeDeclared(false); - config.setCanBeConsumed(false); - config.extendsFrom(this.accessTransformersApi.get()); - }); - - this.injectedInterfacesElements = project.getConfigurations().register(name+"InterfaceInjectionsElements", config -> { - config.attributes(attributes -> - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, INTERFACE_INJECTION_CATEGORY)) - ); - config.setCanBeResolved(false); - config.setCanBeDeclared(false); - config.setCanBeConsumed(false); - config.extendsFrom(this.injectedInterfacesApi.get()); + config.setVisible(false); + config.extendsFrom(this.accessTransformersApi); + })); + + this.injectedInterfacesElements = Memoize.of(() -> { + var c = ConfigurationUtils.consumable(this, name, null, "injectedInterfacesElements", config -> { + config.attributes(attributes -> + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, INTERFACE_INJECTION_CATEGORY)) + ); + config.setVisible(false); + config.extendsFrom(this.injectedInterfacesApi); + }); + return c; }); var workingDirectory = project.getLayout().getBuildDirectory().dir("crochet/installations/" + name); @@ -178,19 +173,18 @@ public LocalMinecraftInstallation(String name, CrochetExtension extension) { model.mapBinaryToSourceWithLineMaps(binary, sources, binaryLineMapped); } - this.binaryArtifactsTask = project.getTasks().register(name + "CrochetMinecraftBinaryArtifacts", TaskGraphExecution.class, task -> { - task.setGroup("crochet setup"); + this.binaryArtifactsTask = TaskUtils.registerInternal(this, TaskGraphExecution.class, name, "crochetMinecraftBinaryArtifacts", task -> { task.getTargets().add(TaskGraphExecution.GraphOutput.of("resources", resources, project.getObjects())); task.getTargets().add(TaskGraphExecution.GraphOutput.of("binarySourceIndependent", binary, project.getObjects())); task.getClasspath().from(project.getConfigurations().named(CrochetProjectPlugin.TASK_GRAPH_RUNNER_CONFIGURATION_NAME)); }); - this.sourcesArtifactsTask = project.getTasks().register(name + "CrochetMinecraftSourcesArtifacts", TaskGraphExecution.class, task -> { + this.sourcesArtifactsTask = TaskUtils.registerInternal(this, TaskGraphExecution.class, name, "crochetMinecraftSourcesArtifacts", task -> { task.copyConfigFrom(binaryArtifactsTask.get()); task.getTargets().add(TaskGraphExecution.GraphOutput.of("sources", sources, project.getObjects())); }); - this.lineMappedBinaryArtifactsTask = project.getTasks().register(name + "CrochetMinecraftLineMappedBinaryArtifacts", TaskGraphExecution.class, task -> { + this.lineMappedBinaryArtifactsTask = TaskUtils.registerInternal(this, TaskGraphExecution.class, name, "crochetMinecraftLineMappedBinaryArtifacts", task -> { task.copyConfigFrom(binaryArtifactsTask.get()); task.getTargets().add(TaskGraphExecution.GraphOutput.of("binary", binaryLineMapped, project.getObjects())); }); @@ -227,29 +221,6 @@ public LocalMinecraftInstallation(String name, CrochetExtension extension) { ); extension.idePostSync.configure(t -> t.dependsOn(binaryArtifactsTask)); - - this.minecraftElements = project.getConfigurations().consumable(name+"MinecraftElements", config -> { - config.extendsFrom(minecraft); - }).get(); - this.minecraftDependenciesElements = project.getConfigurations().consumable(name+"MinecraftDependenciesElements", config -> { - config.extendsFrom(minecraftDependencies); - }).get(); - this.minecraftResourcesElements = project.getConfigurations().consumable(name+"MinecraftResourcesElements", config -> { - config.extendsFrom(minecraftResources); - }).get(); - this.minecraftLineMappedElements = project.getConfigurations().consumable(name+"MinecraftLineMappedElements", config -> { - config.extendsFrom(minecraftLineMapped); - }).get(); - this.nonUpgradableElements = project.getConfigurations().consumable(name+"NonUpgradableElements", config -> { - config.extendsFrom(nonUpgradableDependencies); - }).get(); - - var assetsPropertiesConfiguration = project.getConfigurations().dependencyScope(name+"AssetsProperties"); - project.getDependencies().add(assetsPropertiesConfiguration.getName(), assetsPropertiesFiles); - - this.assetsPropertiesElements = project.getConfigurations().consumable(name+"AssetsPropertiesElements", config -> { - config.extendsFrom(assetsPropertiesConfiguration.get()); - }).get(); } public void share(String externalTag) { @@ -266,15 +237,9 @@ public void share(String externalTag) { @Override public void forFeature(SourceSet sourceSet) { super.forFeature(sourceSet); + forFeatureShared(sourceSet); FeatureUtils.forSourceSetFeature(crochetExtension.project, sourceSet.getName(), context -> { - forFeatureShared(context); - AtomicBoolean atsAdded = new AtomicBoolean(false); - context.withCapabilities(accessTransformersElements.get()); - accessTransformersElements.get().setCanBeConsumed(true); - accessTransformersElements.get().attributes(attributes -> { - attributes.attribute(Category.CATEGORY_ATTRIBUTE, crochetExtension.project.getObjects().named(Category.class, ACCESS_TRANSFORMER_CATEGORY)); - }); accessTransformersElements.get().getDependencies().configureEach(dep -> { if (!atsAdded.compareAndSet(false, true)) { context.publishWithVariants(accessTransformersElements.get()); @@ -287,11 +252,6 @@ public void forFeature(SourceSet sourceSet) { }); AtomicBoolean iisAdded = new AtomicBoolean(false); - context.withCapabilities(injectedInterfacesElements.get()); - injectedInterfacesElements.get().setCanBeConsumed(true); - injectedInterfacesElements.get().attributes(attributes -> { - attributes.attribute(Category.CATEGORY_ATTRIBUTE, crochetExtension.project.getObjects().named(Category.class, INTERFACE_INJECTION_CATEGORY)); - }); injectedInterfacesElements.get().getDependencies().configureEach(dep -> { if (canPublishInjectedInterfaces() && !iisAdded.compareAndSet(false, true)) { context.publishWithVariants(injectedInterfacesElements.get()); @@ -312,8 +272,8 @@ protected AbstractLocalInstallationDependencies makeDependencies(Project proj private static final List INSTALLATION_CONFIGURATION_NAMES = List.of( "AccessTransformers", "AccessTransformersApi", - "InterfaceInjections", - "InterfaceInjectionsApi" + "InjectedInterfaces", + "InjectedInterfacesApi" ); @Override @@ -326,32 +286,60 @@ protected List getInstallationConfigurationNames() { @Override public void forLocalFeature(SourceSet sourceSet) { super.forLocalFeature(sourceSet); + forFeatureShared(sourceSet); + } + + private void forFeatureShared(SourceSet sourceSet) { FeatureUtils.forSourceSetFeature(crochetExtension.project, sourceSet.getName(), context -> { - forFeatureShared(context); + context.withCapabilities(accessTransformersElements.get()); + accessTransformersElements.get().attributes(attributes -> { + attributes.attribute(Category.CATEGORY_ATTRIBUTE, crochetExtension.project.getObjects().named(Category.class, ACCESS_TRANSFORMER_CATEGORY)); + }); + + context.withCapabilities(injectedInterfacesElements.get()); + injectedInterfacesElements.get().attributes(attributes -> { + attributes.attribute(Category.CATEGORY_ATTRIBUTE, crochetExtension.project.getObjects().named(Category.class, INTERFACE_INJECTION_CATEGORY)); + }); }); } - protected final AbstractLocalInstallationDependencies dependencies; + protected final AbstractLocalInstallationDependencies dependencies; - public AbstractLocalInstallationDependencies getDependencies() { + public AbstractLocalInstallationDependencies getDependencies() { return this.dependencies; } - private void forFeatureShared(FeatureUtils.Context context) { - context.withCapabilities(accessTransformersElements.get()); - context.withCapabilities(injectedInterfacesElements.get()); - } - protected abstract String sharingInstallationTypeTag(); protected Map getConfigurationsToLink() { + return configurationsToLink.get(); + } + + private final Supplier> configurationsToLink = Suppliers.memoize(this::makeConfigurationsToLink); + + protected Map makeConfigurationsToLink() { + var assetsPropertiesConfiguration = ConfigurationUtils.dependencyScopeInternal(this, getName(), "assetsProperties", config -> {}); + crochetExtension.project.getDependencies().add(assetsPropertiesConfiguration.getName(), assetsPropertiesFiles); + return Map.of( - "assets-properties", assetsPropertiesElements, - "minecraft", minecraftElements, - "minecraft-dependencies", minecraftDependenciesElements, - "minecraft-resources", minecraftResourcesElements, - "minecraft-line-mapped", minecraftLineMappedElements, - "non-upgradable", nonUpgradableElements + "assets-properties", ConfigurationUtils.consumableInternal(this, getName(), "assetsPropertiesElements", config -> { + config.extendsFrom(assetsPropertiesConfiguration); + }), + "minecraft", ConfigurationUtils.consumableInternal(this, getName(), "minecraftElements", config -> { + config.extendsFrom(minecraft); + }), + "minecraft-dependencies", ConfigurationUtils.consumableInternal(this, getName(), "minecraftDependenciesElements", config -> { + config.extendsFrom(minecraftDependencies); + }), + "minecraft-resources", ConfigurationUtils.consumableInternal(this, getName(), "minecraftResourcesElements", config -> { + config.extendsFrom(minecraftResources); + }), + "minecraft-line-mapped", ConfigurationUtils.consumableInternal(this, getName(), "minecraftLineMappedElements", config -> { + config.extendsFrom(minecraftLineMapped); + }), + "non-upgradable", ConfigurationUtils.consumableInternal(this, getName(), "nonUpgradableElements", config -> { + config.extendsFrom(nonUpgradableDependencies); + }) ); } } diff --git a/src/main/java/dev/lukebemish/crochet/model/Mappings.java b/src/main/java/dev/lukebemish/crochet/model/Mappings.java index 45b0a4b..2193cd9 100644 --- a/src/main/java/dev/lukebemish/crochet/model/Mappings.java +++ b/src/main/java/dev/lukebemish/crochet/model/Mappings.java @@ -17,18 +17,18 @@ @SuppressWarnings("UnstableApiUsage") public interface Mappings extends Dependencies { default MappingsStructure artifact(Dependency dependency) { - var configuration = getProject().getExtensions().getByType(MappingsConfigurationCounter.class).newConfiguration(); - configuration.getDependencies().add(dependency); + var configurations = getProject().getExtensions().getByType(MappingsConfigurationCounter.class).newConfiguration(); + configurations.dependencies().getDependencies().add(dependency); var source = getObjectFactory().newInstance(FileMappingsStructure.class); - source.getMappingsFile().from(configuration); + source.getMappingsFile().from(configurations.classpath()); return source; } default MappingsStructure artifact(Provider dependencyProvider) { - var configuration = getProject().getExtensions().getByType(MappingsConfigurationCounter.class).newConfiguration(); - configuration.getDependencies().addLater(dependencyProvider); + var configurations = getProject().getExtensions().getByType(MappingsConfigurationCounter.class).newConfiguration(); + configurations.dependencies().getDependencies().addLater(dependencyProvider); var source = getObjectFactory().newInstance(FileMappingsStructure.class); - source.getMappingsFile().from(configuration); + source.getMappingsFile().from(configurations.classpath()); return source; } diff --git a/src/main/java/dev/lukebemish/crochet/model/MinecraftInstallation.java b/src/main/java/dev/lukebemish/crochet/model/MinecraftInstallation.java index cc4808a..b1a5553 100644 --- a/src/main/java/dev/lukebemish/crochet/model/MinecraftInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/MinecraftInstallation.java @@ -2,16 +2,14 @@ import dev.lukebemish.crochet.internal.ConfigurationUtils; import dev.lukebemish.crochet.internal.CrochetProjectPlugin; +import dev.lukebemish.crochet.internal.ExtensionHolder; import dev.lukebemish.crochet.internal.FeatureUtils; import dev.lukebemish.crochet.internal.InheritanceMarker; -import org.apache.commons.lang3.StringUtils; import org.gradle.api.Action; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.DependencyScopeConfiguration; +import org.gradle.api.artifacts.ResolvableConfiguration; import org.gradle.api.attributes.AttributeContainer; -import org.gradle.api.attributes.Bundling; -import org.gradle.api.attributes.Category; -import org.gradle.api.attributes.LibraryElements; import org.gradle.api.attributes.Usage; import org.gradle.api.file.ConfigurableFileCollection; import org.gradle.api.plugins.JavaPlugin; @@ -28,11 +26,12 @@ import java.util.Locale; import java.util.Set; -public abstract class MinecraftInstallation implements GeneralizedMinecraftInstallation { +public abstract class MinecraftInstallation extends ExtensionHolder implements GeneralizedMinecraftInstallation { static final String ACCESS_TRANSFORMER_CATEGORY = "accesstransformer"; static final String INTERFACE_INJECTION_CATEGORY = "interfaceinjection"; protected static final String CROSS_PROJECT_SHARING_CAPABILITY_GROUP = "dev.lukebemish.crochet.local.shared-"; + protected static final String CROSS_PROJECT_BUNDLE_CAPABILITY_GROUP = "dev.lukebemish.crochet.local.bundle-"; private final String name; private final Set sourceSets = new LinkedHashSet<>(); @@ -41,22 +40,24 @@ public abstract class MinecraftInstallation implements GeneralizedMinecraftInsta private final Property minecraftVersionProperty; - final Configuration minecraft; - final Configuration minecraftResources; - final Configuration minecraftLineMapped; - final Configuration minecraftDependencies; + final DependencyScopeConfiguration minecraft; + final DependencyScopeConfiguration minecraftResources; + final DependencyScopeConfiguration minecraftLineMapped; + + final DependencyScopeConfiguration minecraftDependencies; final DependencyScopeConfiguration nonUpgradableDependencies; - final Configuration nonUpgradableClientCompileDependencies; - final Configuration nonUpgradableServerCompileDependencies; - final Configuration nonUpgradableClientRuntimeDependencies; - final Configuration nonUpgradableServerRuntimeDependencies; + final ResolvableConfiguration nonUpgradableClientCompileVersioning; + final ResolvableConfiguration nonUpgradableServerCompileVersioning; + final ResolvableConfiguration nonUpgradableClientRuntimeVersioning; + final ResolvableConfiguration nonUpgradableServerRuntimeVersioning; final ConfigurableFileCollection assetsPropertiesFiles; - @SuppressWarnings("UnstableApiUsage") @Inject public MinecraftInstallation(String name, CrochetExtension extension) { + super(extension); + this.name = name; this.crochetExtension = extension; @@ -68,69 +69,69 @@ public MinecraftInstallation(String name, CrochetExtension extension) { this.distribution.finalizeValueOnRead(); this.distribution.convention(InstallationDistribution.JOINED); - this.minecraftDependencies = project.getConfigurations().create("crochet"+ StringUtils.capitalize(name)+"MinecraftDependencies"); + this.minecraftDependencies = ConfigurationUtils.dependencyScope(this, name, null, "minecraftDependencies", config -> {}); + Configuration minecraftDependenciesVersioning = ConfigurationUtils.resolvableInternal(this, name, "minecraftDependenciesVersioning", config -> { + config.attributes(attributes -> { + attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, InstallationDistribution.CLIENT.neoAttributeValue()); + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_API)); + }); + config.extendsFrom(minecraftDependencies); + }); // Create early so getMinecraft provider works right this.minecraftVersionProperty = project.getObjects().property(String.class); - this.minecraftVersionProperty.set(minecraftDependencies.getIncoming().getResolutionResult().getRootComponent().map(ConfigurationUtils::extractMinecraftVersion)); + this.minecraftVersionProperty.set(minecraftDependenciesVersioning.getIncoming().getResolutionResult().getRootComponent().map(ConfigurationUtils::extractMinecraftVersion)); - this.nonUpgradableDependencies = project.getConfigurations().dependencyScope("crochet"+StringUtils.capitalize(name)+"NonUpgradableDependencies", config -> config.extendsFrom(minecraftDependencies)).get(); + this.nonUpgradableDependencies = ConfigurationUtils.dependencyScopeInternal(this, name, "nonUpgradableDependencies", config -> { + config.extendsFrom(minecraftDependencies); + }); Action sharedAttributeAction = attributes -> { - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + /*attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL)); - attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR)); + attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR));*/ + // TODO: is this necessary? It _shouldn't_ be... }; - this.nonUpgradableClientCompileDependencies = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"NonUpgradableClientCompileDependencies", config -> { - config.extendsFrom(this.nonUpgradableDependencies); - config.setCanBeConsumed(false); + this.nonUpgradableClientCompileVersioning = ConfigurationUtils.resolvableInternal(this, name, "nonUpgradableClientCompileVersioning", config -> { + config.extendsFrom(nonUpgradableDependencies); + config.attributes(sharedAttributeAction); config.attributes(attributes -> { - sharedAttributeAction.execute(attributes); attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, InstallationDistribution.CLIENT.neoAttributeValue()); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_API)); }); }); - this.nonUpgradableServerCompileDependencies = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"NonUpgradableServerCompileDependencies", config -> { - config.extendsFrom(this.nonUpgradableDependencies); - config.setCanBeConsumed(false); + this.nonUpgradableServerCompileVersioning = ConfigurationUtils.resolvableInternal(this, name, "nonUpgradableServerCompileVersioning", config -> { + config.extendsFrom(nonUpgradableDependencies); + config.attributes(sharedAttributeAction); config.attributes(attributes -> { - sharedAttributeAction.execute(attributes); attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, InstallationDistribution.SERVER.neoAttributeValue()); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_API)); }); }); - this.nonUpgradableClientRuntimeDependencies = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"NonUpgradableClientRuntimeDependencies", config -> { - config.extendsFrom(this.nonUpgradableDependencies); - config.setCanBeConsumed(false); + this.nonUpgradableClientRuntimeVersioning = ConfigurationUtils.resolvableInternal(this, name, "nonUpgradableClientRuntimeVersioning", config -> { + config.extendsFrom(nonUpgradableDependencies); + config.attributes(sharedAttributeAction); config.attributes(attributes -> { - sharedAttributeAction.execute(attributes); attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, InstallationDistribution.CLIENT.neoAttributeValue()); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); }); }); - this.nonUpgradableServerRuntimeDependencies = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"NonUpgradableServerRuntimeDependencies", config -> { - config.extendsFrom(this.nonUpgradableDependencies); - config.setCanBeConsumed(false); + this.nonUpgradableServerRuntimeVersioning = ConfigurationUtils.resolvableInternal(this, name, "nonUpgradableServerRuntimeVersioning", config -> { + config.extendsFrom(nonUpgradableDependencies); + config.attributes(sharedAttributeAction); config.attributes(attributes -> { - sharedAttributeAction.execute(attributes); attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, InstallationDistribution.SERVER.neoAttributeValue()); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); }); }); - - this.minecraftResources = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"MinecraftResources"); - this.minecraft = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"Minecraft", config -> { - config.setCanBeConsumed(false); + this.minecraftResources = ConfigurationUtils.dependencyScopeInternal(this, name, "minecraftResources", config -> {}); + this.minecraft = ConfigurationUtils.dependencyScopeInternal(this, name, "minecraft", config -> { config.extendsFrom(minecraftDependencies); config.extendsFrom(minecraftResources); - config.attributes(attributes -> attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, getDistribution().map(InstallationDistribution::neoAttributeValue))); }); - - this.minecraftLineMapped = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"MinecraftLineMapped", config -> { - config.setCanBeConsumed(false); + this.minecraftLineMapped = ConfigurationUtils.dependencyScopeInternal(this, name, "minecraftLineMapped", config -> { config.extendsFrom(minecraftDependencies); config.extendsFrom(minecraftResources); - config.attributes(attributes -> attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, getDistribution().map(InstallationDistribution::neoAttributeValue))); }); } @@ -198,8 +199,8 @@ private void forFeatureShared(FeatureUtils.Context context) { project.getConfigurations().named(sourceSet.getTaskName(null, JavaPlugin.COMPILE_CLASSPATH_CONFIGURATION_NAME), config -> { config.extendsFrom(minecraft); config.shouldResolveConsistentlyWith(switch (getDistribution().get()) { - case CLIENT, JOINED -> nonUpgradableClientCompileDependencies; - case SERVER, COMMON -> nonUpgradableServerCompileDependencies; + case CLIENT, JOINED -> nonUpgradableClientCompileVersioning; + case SERVER, COMMON -> nonUpgradableServerCompileVersioning; }); config.attributes(attributesAction); }); @@ -242,8 +243,8 @@ void forRun(Run run, RunType runType) { } run.classpath.shouldResolveConsistentlyWith(switch (runType) { - case CLIENT, DATA -> nonUpgradableClientRuntimeDependencies; - case SERVER -> nonUpgradableServerRuntimeDependencies; + case CLIENT, DATA -> nonUpgradableClientRuntimeVersioning; + case SERVER -> nonUpgradableServerRuntimeVersioning; }); } } diff --git a/src/main/java/dev/lukebemish/crochet/model/NeoFormInstallation.java b/src/main/java/dev/lukebemish/crochet/model/NeoFormInstallation.java index d44d42a..a313ab9 100644 --- a/src/main/java/dev/lukebemish/crochet/model/NeoFormInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/NeoFormInstallation.java @@ -1,29 +1,27 @@ package dev.lukebemish.crochet.model; +import dev.lukebemish.crochet.internal.ConfigurationUtils; import dev.lukebemish.crochet.internal.CrochetProjectPlugin; import dev.lukebemish.crochet.internal.CrochetRepositoriesPlugin; import dev.lukebemish.crochet.internal.metadata.pistonmeta.PistonMetaMetadataRule; import dev.lukebemish.crochet.internal.tasks.NeoFormInstallationArtifacts; -import org.apache.commons.lang3.StringUtils; import org.gradle.api.Action; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; import org.gradle.api.artifacts.ModuleDependency; -import org.gradle.api.attributes.Category; import org.gradle.api.attributes.Usage; import org.gradle.api.provider.Property; import org.gradle.api.tasks.SourceSet; import javax.inject.Inject; import java.util.ArrayList; -import java.util.Map; import java.util.stream.Collectors; public abstract class NeoFormInstallation extends LocalMinecraftInstallation { final Project project; final NeoFormInstallationArtifacts neoFormConfigMaker; - final Configuration parchmentData; + final Configuration parchment; final Configuration neoFormConfigDependencies; final Configuration neoFormConfig; @@ -33,91 +31,56 @@ public NeoFormInstallation(String name, CrochetExtension extension) { this.project = extension.project; - var minecraftPistonMeta = project.getConfigurations().dependencyScope("crochet"+ StringUtils.capitalize(name)+"PistonMetaDownloads"); - var clientJarPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"ClientJarPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.exclude(Map.of( - "group", CrochetRepositoriesPlugin.MOJANG_STUBS_GROUP, - "module", PistonMetaMetadataRule.MINECRAFT_DEPENDENCIES - )); - c.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "client"); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - }); - }); - var serverJarPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"ServerJarPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.exclude(Map.of( - "group", CrochetRepositoriesPlugin.MOJANG_STUBS_GROUP, - "module", PistonMetaMetadataRule.MINECRAFT_DEPENDENCIES - )); - c.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "server"); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); - }); - }); - var clientMappingsPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"ClientMappingsPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "client"); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, "mappings")); - }); - }); - var serverMappingsPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"ServerMappingsPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.attributes(attributes -> { - attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "server"); - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, "mappings")); - }); - }); - var versionJsonPistonMeta = project.getConfigurations().resolvable("crochet"+StringUtils.capitalize(name)+"VersionJsonPistonMetaDownloads", c -> { - c.extendsFrom(minecraftPistonMeta.get()); - c.attributes(attributes -> { - attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, "versionjson")); - }); - }); + var minecraftPistonMeta = ConfigurationUtils.pistonMetaDependencies(this, name); + var clientJarPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.CLIENT_JAR); + var serverJarPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.SERVER_JAR); + var clientMappingsPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.CLIENT_MAPPINGS); + var serverMappingsPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.SERVER_MAPPINGS); + var versionJsonPistonMeta = ConfigurationUtils.pistonMeta(this, name, minecraftPistonMeta, ConfigurationUtils.PistonMetaPiece.VERSION_JSON); project.getDependencies().addProvider(minecraftPistonMeta.getName(), getMinecraft().map(v -> CrochetRepositoriesPlugin.MOJANG_STUBS_GROUP+":"+PistonMetaMetadataRule.MINECRAFT+":"+v)); - this.parchmentData = project.getConfigurations().create(name+"ParchmentData"); - this.parchmentData.fromDependencyCollector(getDependencies().getParchment()); + this.parchment = ConfigurationUtils.dependencyScope(this, name, null, "parchment", c -> {}); + this.parchment.fromDependencyCollector(getDependencies().getParchment()); + + var parchmentData = ConfigurationUtils.resolvableInternal(this, name, "parchmentData", c -> { + c.extendsFrom(parchment); + }); this.neoFormConfigMaker = project.getObjects().newInstance(NeoFormInstallationArtifacts.class); neoFormConfigMaker.getAccessTransformers().from(this.accessTransformersPath); neoFormConfigMaker.getInjectedInterfaces().from(this.injectedInterfacesPath); - neoFormConfigMaker.getParchment().from(this.parchmentData); + neoFormConfigMaker.getParchment().from(parchmentData); neoFormConfigMaker.getRecompile().set(this.getRecompile()); this.binaryArtifactsTask.configure(t -> t.getConfigMaker().set(neoFormConfigMaker)); - var decompileCompileClasspath = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"NeoformCompileClasspath", config -> { - config.extendsFrom(minecraftDependencies); - config.setCanBeConsumed(false); - config.attributes(attributes -> { + var decompileCompileClasspath = ConfigurationUtils.resolvableInternal(this, name, "neoFormCompileClasspath", c -> { + c.extendsFrom(minecraftDependencies); + c.attributes(attributes -> { attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "client"); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_API)); }); }); - var decompileRuntimeClasspath = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"NeoformRuntimeClasspath", config -> { - config.extendsFrom(minecraftDependencies); - config.setCanBeConsumed(false); - config.attributes(attributes -> { + var decompileRuntimeClasspath = ConfigurationUtils.resolvableInternal(this, name, "neoFormRuntimeClasspath", c -> { + c.extendsFrom(minecraftDependencies); + c.attributes(attributes -> { attributes.attribute(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, "client"); attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); }); }); - this.neoFormConfigDependencies = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"NeoformConfig", config -> { - config.setCanBeConsumed(false); - config.attributes(attributes -> attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, getDistribution().map(InstallationDistribution::neoAttributeValue))); + var neoForm = ConfigurationUtils.dependencyScope(this, name, null, "neoForm", c -> {}); + this.neoFormConfigDependencies = ConfigurationUtils.resolvableInternal(this, name, "neoFormConfigDependencies", c -> { + c.extendsFrom(neoForm); + c.attributes(attributes -> attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, getDistribution().map(InstallationDistribution::neoAttributeValue))); }); - this.neoFormConfig = project.getConfigurations().create("crochet"+StringUtils.capitalize(name)+"Neoform", config -> { - config.extendsFrom(this.neoFormConfigDependencies); - config.setCanBeConsumed(false); - config.setTransitive(false); - config.attributes(attributes -> attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, getDistribution().map(InstallationDistribution::neoAttributeValue))); + this.neoFormConfig = ConfigurationUtils.resolvableInternal(this, name, "neoFormConfig", c -> { + c.extendsFrom(neoForm); + c.setTransitive(false); + c.attributes(attributes -> attributes.attributeProvider(CrochetProjectPlugin.NEO_DISTRIBUTION_ATTRIBUTE, getDistribution().map(InstallationDistribution::neoAttributeValue))); }); - this.neoFormConfigDependencies.fromDependencyCollector(getDependencies().getNeoForm()); - this.minecraftDependencies.getDependencies().addAllLater(project.provider(() -> this.neoFormConfigDependencies.getAllDependencies().stream().map(d -> { + neoForm.fromDependencyCollector(getDependencies().getNeoForm()); + this.minecraftDependencies.getDependencies().addAllLater(project.provider(() -> neoForm.getAllDependencies().stream().map(d -> { var copy = d.copy(); if (copy instanceof ModuleDependency moduleDependency) { copy = moduleDependency.capabilities(capabilities -> { @@ -126,21 +89,20 @@ public NeoFormInstallation(String name, CrochetExtension extension) { } return copy; }).collect(Collectors.toCollection(ArrayList::new)))); + this.minecraftDependencies.getDependencyConstraints().addAllLater(project.provider(neoForm::getDependencyConstraints)); neoFormConfigMaker.getNeoForm().from(neoFormConfig); - this.minecraftDependencies.getDependencyConstraints().addAllLater(project.provider(this.neoFormConfigDependencies::getDependencyConstraints)); - this.binaryArtifactsTask.configure(task -> { task.artifactsConfiguration(decompileCompileClasspath); task.artifactsConfiguration(decompileRuntimeClasspath); task.artifactsConfiguration(neoFormConfigDependencies); - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-version-json", versionJsonPistonMeta.get()); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-version-json", versionJsonPistonMeta); // Both for now as the config is always JOINED - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-client-jar", clientJarPistonMeta.get()); - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-server-jar", serverJarPistonMeta.get()); - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-client-mappings", clientMappingsPistonMeta.get()); - task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-server-mappings", serverMappingsPistonMeta.get()); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-client-jar", clientJarPistonMeta); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-server-jar", serverJarPistonMeta); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-client-mappings", clientMappingsPistonMeta); + task.singleFileConfiguration("dev.lukebemish.crochet.internal:minecraft-server-mappings", serverMappingsPistonMeta); task.artifactsConfiguration(project.getConfigurations().getByName(CrochetProjectPlugin.TASK_GRAPH_RUNNER_TOOLS_CONFIGURATION_NAME)); }); @@ -168,7 +130,9 @@ void forRun(Run run, RunType runType) { super.forRun(run, runType); run.argFilesTask.configure(task -> task.getMinecraftVersion().set(getMinecraft())); - run.classpath.fromDependencyCollector(run.getImplementation()); + var implementation = ConfigurationUtils.dependencyScopeInternal(project, run.getName(), "runImplementation", c -> {}); + implementation.fromDependencyCollector(run.getImplementation()); + run.classpath.extendsFrom(implementation); switch (runType) { case CLIENT -> { diff --git a/src/main/java/dev/lukebemish/crochet/model/Run.java b/src/main/java/dev/lukebemish/crochet/model/Run.java index db614a1..0c2845c 100644 --- a/src/main/java/dev/lukebemish/crochet/model/Run.java +++ b/src/main/java/dev/lukebemish/crochet/model/Run.java @@ -7,7 +7,7 @@ import org.apache.commons.lang3.StringUtils; import org.gradle.api.Action; import org.gradle.api.Named; -import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.ResolvableConfiguration; import org.gradle.api.artifacts.dsl.Dependencies; import org.gradle.api.artifacts.dsl.DependencyCollector; import org.gradle.api.attributes.Category; @@ -41,7 +41,7 @@ public abstract class Run implements Named, Dependencies { final TaskProvider argFilesTask; private MinecraftInstallation installation; - final Configuration classpath; + final ResolvableConfiguration classpath; @Inject protected abstract JavaToolchainService getToolchainService(); @@ -49,8 +49,7 @@ public abstract class Run implements Named, Dependencies { @Inject public Run(String name) { this.name = name; - this.classpath = getProject().getConfigurations().maybeCreate("crochetRun"+StringUtils.capitalize(name)+"Classpath"); - classpath.setCanBeConsumed(false); + this.classpath = ConfigurationUtils.resolvableInternal(getProject(), name, "runClasspath", c -> {}); classpath.attributes(attributes -> { attributes.attribute(Usage.USAGE_ATTRIBUTE, getProject().getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); attributes.attribute(Category.CATEGORY_ATTRIBUTE, getProject().getObjects().named(Category.class, Category.LIBRARY)); diff --git a/src/main/java/dev/lukebemish/crochet/model/SettingsMinecraftInstallation.java b/src/main/java/dev/lukebemish/crochet/model/SettingsMinecraftInstallation.java index e6094cc..066e701 100644 --- a/src/main/java/dev/lukebemish/crochet/model/SettingsMinecraftInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/SettingsMinecraftInstallation.java @@ -4,6 +4,7 @@ import dev.lukebemish.crochet.internal.CrochetPlugin; import dev.lukebemish.crochet.internal.CrochetRepositoriesPlugin; import org.gradle.api.Action; +import org.gradle.api.Project; import org.gradle.api.artifacts.VersionConstraint; import org.gradle.api.initialization.Settings; import org.gradle.api.provider.Property; @@ -22,25 +23,32 @@ public SettingsMinecraftInstallation(String name, Settings settings) { if (project.getPath().equals(project.getIsolated().getRootProject().getPath())) { project.getPluginManager().apply(CrochetPlugin.class); var crochet = project.getExtensions().getByType(CrochetExtension.class); - makeInstallation(name, crochet, this::configureInstallation); + makeInstallation(crochet, this::configureInstallation); } project.getPluginManager().withPlugin("dev.lukebemish.crochet", plugin -> { var crochet = project.getExtensions().getByType(CrochetExtension.class); crochet.addSharedInstallation(name); + generalSetup(crochet); }); }); getDistribution().convention(InstallationDistribution.JOINED); } - protected abstract void makeInstallation(String name, CrochetExtension extension, Action action); + protected abstract void makeInstallation(CrochetExtension extension, Action action); + + protected void generalSetup(CrochetExtension crochet) {} protected void configureInstallation(R installation) { installation.getDistribution().set(getDistribution()); - installation.share(getName()); + installation.share(getInstallationName()); } protected final List> dependencyActions = new ArrayList<>(); + protected String getInstallationName() { + return getName()+"Shared"; + } + public void dependencies(Action action) { dependencyActions.add(action); } @@ -117,8 +125,8 @@ public Vanilla(String name, Settings settings) { } @Override - protected void makeInstallation(String name, CrochetExtension extension, Action action) { - extension.vanillaInstallation(name, action); + protected void makeInstallation(CrochetExtension extension, Action action) { + extension.vanillaInstallation(this.getInstallationName(), action); } @Override @@ -136,9 +144,26 @@ public Fabric(String name, Settings settings) { super(name, settings); } + private final List> bundleAction = new ArrayList<>(); + @Override - protected void makeInstallation(String name, CrochetExtension extension, Action action) { - extension.fabricInstallation(name, action); + protected void generalSetup(CrochetExtension crochet) { + super.generalSetup(crochet); + var bundle = makeDependencyBundle(this.getInstallationName(), crochet.project); + crochet.addBundle(bundle); + } + + @Override + protected void makeInstallation(CrochetExtension extension, Action action) { + extension.fabricInstallation(this.getInstallationName(), action); + } + + private FabricDependencyBundle makeDependencyBundle(String name, Project project) { + return project.getObjects().newInstance(FabricDependencyBundle.class, name, (Action) dependencies -> { + for (var action : bundleAction) { + action.execute(dependencies); + } + }); } @Override @@ -147,6 +172,12 @@ protected void configureInstallation(FabricInstallation installation) { for (var action : this.dependencyActions) { action.execute(installation.getDependencies()); } + var bundle = makeDependencyBundle(installation.getName(), installation.project); + installation.makeBundle(bundle); + } + + public void bundle(Action action) { + bundleAction.add(action); } } @@ -157,8 +188,8 @@ public NeoForm(String name, Settings settings) { } @Override - protected void makeInstallation(String name, CrochetExtension extension, Action action) { - extension.neoFormInstallation(name, action); + protected void makeInstallation(CrochetExtension extension, Action action) { + extension.neoFormInstallation(this.getInstallationName(), action); } @Override diff --git a/src/main/java/dev/lukebemish/crochet/model/VanillaInstallation.java b/src/main/java/dev/lukebemish/crochet/model/VanillaInstallation.java index 1283461..278dc3f 100644 --- a/src/main/java/dev/lukebemish/crochet/model/VanillaInstallation.java +++ b/src/main/java/dev/lukebemish/crochet/model/VanillaInstallation.java @@ -6,13 +6,13 @@ import javax.inject.Inject; public abstract class VanillaInstallation extends AbstractVanillaInstallation { - private final VanillaInstallationRunLogic runLogic; + private final VanillaInstallationLogic logic; @Inject public VanillaInstallation(String name, CrochetExtension extension) { super(name, extension); - runLogic = new VanillaInstallationRunLogic(this) {}; + logic = new VanillaInstallationLogic(this) {}; } @Override @@ -32,7 +32,7 @@ public void dependencies(Action action) @Override void forRun(Run run, RunType runType) { super.forRun(run, runType); - runLogic.forRun(run, runType); + logic.forRun(run, runType); } @Override diff --git a/src/main/java/dev/lukebemish/crochet/model/VanillaInstallationRunLogic.java b/src/main/java/dev/lukebemish/crochet/model/VanillaInstallationLogic.java similarity index 82% rename from src/main/java/dev/lukebemish/crochet/model/VanillaInstallationRunLogic.java rename to src/main/java/dev/lukebemish/crochet/model/VanillaInstallationLogic.java index 9f5d0a8..7af2019 100644 --- a/src/main/java/dev/lukebemish/crochet/model/VanillaInstallationRunLogic.java +++ b/src/main/java/dev/lukebemish/crochet/model/VanillaInstallationLogic.java @@ -1,13 +1,14 @@ package dev.lukebemish.crochet.model; +import dev.lukebemish.crochet.internal.ConfigurationUtils; import dev.lukebemish.crochet.internal.CrochetProjectPlugin; import org.gradle.api.Project; -public abstract class VanillaInstallationRunLogic { +abstract class VanillaInstallationLogic { private final MinecraftInstallation minecraftInstallation; private final Project project; - public VanillaInstallationRunLogic(MinecraftInstallation minecraftInstallation) { + VanillaInstallationLogic(MinecraftInstallation minecraftInstallation) { this.minecraftInstallation = minecraftInstallation; this.project = minecraftInstallation.crochetExtension.project; } @@ -15,7 +16,9 @@ public VanillaInstallationRunLogic(MinecraftInstallation minecraftInstallation) void forRun(Run run, RunType runType) { run.argFilesTask.configure(task -> task.getMinecraftVersion().set(minecraftInstallation.getMinecraft())); - run.classpath.fromDependencyCollector(run.getImplementation()); + var implementation = ConfigurationUtils.dependencyScopeInternal(project, run.getName(), "runImplementation", c -> {}); + implementation.fromDependencyCollector(run.getImplementation()); + run.classpath.extendsFrom(implementation); project.afterEvaluate(p -> { if (run.getAvoidNeedlessDecompilation().get()) { diff --git a/test/build.gradle b/test/build.gradle index 00e0406..2409d11 100644 --- a/test/build.gradle +++ b/test/build.gradle @@ -15,8 +15,8 @@ repositories { url = uri("https://maven.parchmentmc.org/") } maven { - name 'Staging' - url 'https://maven.lukebemish.dev/staging/' + name = 'Staging' + url = 'https://maven.lukebemish.dev/staging/' } } @@ -65,21 +65,11 @@ crochet { } } - fabricInstallation('fabricYarn') { - minecraft = '1.21.4' + /*externalFabricInstallation('fabricYarn') { + consume(sharedInstallations.yarn) - dependencies { - loader 'net.fabricmc:fabric-loader:0.16.9' - mappings chained { - add intermediary() - add(artifact 'net.fabricmc:yarn:1.21.4+build.4:v2') - } - } - - share('yarn') - - forFeature(sourceSets.yarn) - } + //forFeature(sourceSets.yarn) + }*/ neoFormInstallation('neoForm') { dependencies { @@ -90,9 +80,6 @@ crochet { } runs { - vanillaClient { - client installations.vanilla - } fabricClient { client splitSourceSets.main.client implementation project(':') @@ -101,14 +88,14 @@ crochet { server splitSourceSets.main.server implementation project(':') } - fabricYarnClient { + /*fabricYarnClient { client installations.fabricYarn implementation(project(':')) { capabilities { requireFeature('yarn') } } - } + }*/ neoFormClient { client installations.neoForm } diff --git a/test/consumes-shared/build.gradle b/test/consumes-shared/build.gradle index 4de8149..7d654f5 100644 --- a/test/consumes-shared/build.gradle +++ b/test/consumes-shared/build.gradle @@ -15,8 +15,8 @@ repositories { url = uri("https://maven.parchmentmc.org/") } maven { - name 'Staging' - url 'https://maven.lukebemish.dev/staging/' + name = 'Staging' + url = 'https://maven.lukebemish.dev/staging/' } } @@ -27,7 +27,7 @@ version = '1.0.0' crochet { externalVanillaInstallation('vanilla') { - consume("vanilla") + consume(sharedInstallations.vanilla) } runs { diff --git a/test/gradle.properties b/test/gradle.properties index 89e3818..9d4fdc8 100644 --- a/test/gradle.properties +++ b/test/gradle.properties @@ -2,4 +2,10 @@ org.gradle.jvmargs=-Xmx3g org.gradle.configuration-cache=true org.gradle.parallel=true +# For debugging +org.gradle.logging.stacktrace=all + useLocalMavenForTesting=false + +dev.lukebemish.crochet.validation.validate-configurations-path=../codedocs/configurations.md +dev.lukebemish.crochet.validation.validate-configurations-mode=validate diff --git a/test/gradle/wrapper/gradle-wrapper.properties b/test/gradle/wrapper/gradle-wrapper.properties index cea7a79..e18bc25 100644 --- a/test/gradle/wrapper/gradle-wrapper.properties +++ b/test/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/test/settings.gradle b/test/settings.gradle index 4a220e9..ba23e4d 100644 --- a/test/settings.gradle +++ b/test/settings.gradle @@ -8,8 +8,8 @@ pluginManagement { gradlePluginPortal() maven { - name 'Staging' - url 'https://maven.lukebemish.dev/staging/' + name = 'Staging' + url = 'https://maven.lukebemish.dev/staging/' } } } @@ -31,4 +31,20 @@ crochet { vanillaInstallation('vanilla') { minecraft = '1.21.1' } + + fabricInstallation('yarn') { + minecraft = '1.21.4' + + dependencies { + loader 'net.fabricmc:fabric-loader:0.16.9' + mappings chained { + add intermediary() + add(artifact 'net.fabricmc:yarn:1.21.4+build.4:v2') + } + } + + bundle { + modApi 'net.fabricmc.fabric-api:fabric-api:0.114.1+1.21.4' + } + } } diff --git a/test/src/yarn/java/test/Test.java b/test/src/yarn/java/test/Test.java index 95ef120..a0e0722 100644 --- a/test/src/yarn/java/test/Test.java +++ b/test/src/yarn/java/test/Test.java @@ -6,5 +6,8 @@ public class Test implements ModInitializer { @Override public void onInitialize() { System.out.println("Crochet loaded the fabric test mod!"); + ServerLifecyceEvents.SERVER_STARTED.register(server -> { + System.out.println("Hello from server start!"); + }); } } diff --git a/test/subproject/build.gradle b/test/subproject/build.gradle index 28f70f7..3800f6e 100644 --- a/test/subproject/build.gradle +++ b/test/subproject/build.gradle @@ -15,8 +15,8 @@ repositories { url = uri("https://maven.parchmentmc.org/") } maven { - name 'Staging' - url 'https://maven.lukebemish.dev/staging/' + name = 'Staging' + url = 'https://maven.lukebemish.dev/staging/' } } diff --git a/tools/build.gradle b/tools/build.gradle index f1aa1c9..b9c57a0 100644 --- a/tools/build.gradle +++ b/tools/build.gradle @@ -1,6 +1,7 @@ plugins { id 'java-library' id 'maven-publish' + id 'checkstyle' id 'signing' id 'com.gradleup.shadow' id 'dev.lukebemish.managedversioning' @@ -27,8 +28,8 @@ repositories { url = 'https://maven.fabricmc.net/' } maven { - name 'Staging' - url 'https://maven.lukebemish.dev/staging/' + name = 'Staging' + url = 'https://maven.lukebemish.dev/staging/' } } diff --git a/tools/src/main/java/dev/lukebemish/crochet/tools/TransformAccessWideners.java b/tools/src/main/java/dev/lukebemish/crochet/tools/TransformAccessWideners.java index fe0b0bd..5e8d1a9 100644 --- a/tools/src/main/java/dev/lukebemish/crochet/tools/TransformAccessWideners.java +++ b/tools/src/main/java/dev/lukebemish/crochet/tools/TransformAccessWideners.java @@ -4,7 +4,7 @@ import net.fabricmc.accesswidener.AccessWidenerRemapper; import net.fabricmc.accesswidener.AccessWidenerVisitor; import net.neoforged.srgutils.IMappingFile; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor;