diff --git a/.github/workflows/build-prs.yml b/.github/workflows/build-prs.yml new file mode 100644 index 000000000..5fad0b6d4 --- /dev/null +++ b/.github/workflows/build-prs.yml @@ -0,0 +1,23 @@ +# File generated by the GradleUtils `setupGitHubActionsWorkflows` task, avoid modifying it directly +# The template can be found at https://github.com/neoforged/GradleUtils/blob/287df22f683ed8422bc93deb9eb4a62099cc061f/src/actionsTemplate/resources/.github/workflows/build-prs.yml + +name: Build PRs + +on: + pull_request: + types: + - synchronize + - opened + - ready_for_review + - reopened + push: + branches: + - 'feature/**' + workflow_dispatch: + +jobs: + build: + uses: neoforged/actions/.github/workflows/build-prs.yml@main + with: + java: 8 + gradle_tasks: assemble diff --git a/.github/workflows/publish-prs.yml b/.github/workflows/publish-prs.yml index 213ef97d9..81e3e63b1 100644 --- a/.github/workflows/publish-prs.yml +++ b/.github/workflows/publish-prs.yml @@ -5,7 +5,7 @@ name: Publish PRs to GitHub Packages on: workflow_run: - workflows: ['Build Pull Request'] + workflows: ['Build PRs'] types: - completed issue_comment: @@ -24,7 +24,7 @@ jobs: uses: neoforged/actions/.github/workflows/publish-prs.yml@main with: artifact_base_path: net/neoforged/gradle/ - uploader_workflow_name: 'Build Pull Request' + uploader_workflow_name: 'Build PRs' secrets: PR_PUBLISHING_GH_APP_ID: ${{ secrets.PR_PUBLISHING_GH_APP_ID }} PR_PUBLISHING_GH_APP_KEY: ${{ secrets.PR_PUBLISHING_GH_APP_KEY }} diff --git a/.github/workflows/pr.yml b/.github/workflows/test-prs.yml similarity index 95% rename from .github/workflows/pr.yml rename to .github/workflows/test-prs.yml index d22125d74..4e7afb9a3 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/test-prs.yml @@ -1,5 +1,5 @@ -name: 'Build Pull Request' -run-name: Build for PR ${{ github.event.pull_request.number }} +name: Test PRs +run-name: Tests for PR ${{ github.event.pull_request.number }} on: pull_request: @@ -61,9 +61,6 @@ jobs: - name: Build run: ./gradlew --info -s -x assemble - - name: Publish artifacts - uses: neoforged/action-pr-publishing/upload@v1 - test: name: Test runs-on: ubuntu-latest diff --git a/.github/workflows/test-report.yml b/.github/workflows/test-report.yml index a8abebcde..f1a0dc7ec 100644 --- a/.github/workflows/test-report.yml +++ b/.github/workflows/test-report.yml @@ -1,7 +1,7 @@ name: 'Test Report' on: workflow_run: - workflows: ['Build Pull Request'] + workflows: ['Test PRs'] types: - completed permissions: diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/AbstractDependencyManagementObject.java b/common/src/main/java/net/neoforged/gradle/common/dependency/AbstractDependencyManagementObject.java index 79bb79be7..423c95b97 100644 --- a/common/src/main/java/net/neoforged/gradle/common/dependency/AbstractDependencyManagementObject.java +++ b/common/src/main/java/net/neoforged/gradle/common/dependency/AbstractDependencyManagementObject.java @@ -4,44 +4,52 @@ import net.neoforged.gradle.dsl.common.dependency.DependencyManagementObject; import org.gradle.api.Project; import org.gradle.api.artifacts.Dependency; -import org.gradle.api.artifacts.ModuleDependency; -import org.gradle.api.artifacts.ResolvedDependency; +import org.gradle.api.artifacts.dsl.DependencyFactory; import org.gradle.api.specs.Spec; import org.gradle.api.specs.Specs; +import org.jetbrains.annotations.Nullable; -import java.util.regex.Pattern; +import javax.inject.Inject; +import java.util.Objects; -public class AbstractDependencyManagementObject implements DependencyManagementObject { +@SuppressWarnings("UnstableApiUsage") +public abstract class AbstractDependencyManagementObject implements DependencyManagementObject { + @Inject + protected abstract DependencyFactory getDependencyFactory(); - protected final Project project; - - public AbstractDependencyManagementObject(Project project) { - this.project = project; + protected static ArtifactIdentifier createArtifactIdentifier(final Dependency dependency) { + return new ArtifactIdentifier(dependency.getGroup(), dependency.getName(), dependency.getVersion()); } - protected static ArtifactIdentifier createArtifactIdentifier(final ResolvedDependency dependency) { - return new ArtifactIdentifier(dependency.getModuleGroup(), dependency.getModuleName(), dependency.getModuleVersion()); + @Override + public Spec dependency(Project project) { + return dependency(getDependencyFactory().create(project)); } - protected static ArtifactIdentifier createArtifactIdentifier(final ModuleDependency dependency) { - return new ArtifactIdentifier(dependency.getGroup(), dependency.getName(), dependency.getVersion()); + @Override + public Spec dependency(CharSequence dependencyNotation) { + return dependency(getDependencyFactory().create(dependencyNotation)); } - public Spec dependency(Object notation) { - return dependency(project.getDependencies().create(notation)); + @Override + public Spec dependency(@Nullable String group, String name, @Nullable String version) { + return dependency(getDependencyFactory().create(group, name, version)); } + @Override public Spec dependency(Dependency dependency) { + ArtifactIdentifier identifier = createArtifactIdentifier(dependency); + return this.dependency(new Closure(null) { @SuppressWarnings("ConstantConditions") @Override public Boolean call(final Object it) { if (it instanceof DependencyManagementObject.ArtifactIdentifier) { - final DependencyManagementObject.ArtifactIdentifier identifier = (DependencyManagementObject.ArtifactIdentifier) it; - return (dependency.getGroup() == null || Pattern.matches(dependency.getGroup(), identifier.getGroup())) && - (dependency.getName() == null || Pattern.matches(dependency.getName(), identifier.getName())) && - (dependency.getVersion() == null || Pattern.matches(dependency.getVersion(), identifier.getVersion())); + final DependencyManagementObject.ArtifactIdentifier id = (DependencyManagementObject.ArtifactIdentifier) it; + return (identifier.getGroup() == null || id.getGroup() == null || Objects.equals(identifier.getGroup(), id.getGroup())) && + (identifier.getName() == null || id.getName() == null || Objects.equals(identifier.getName(), id.getName())) && + (identifier.getVersion() == null || id.getVersion() == null || Objects.equals(identifier.getVersion(), id.getVersion())); } return false; @@ -49,6 +57,7 @@ public Boolean call(final Object it) { }); } + @Override public Spec dependency(Closure spec) { return Specs.convertClosureToSpec(spec); } diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/DefaultDependencyFilter.java b/common/src/main/java/net/neoforged/gradle/common/dependency/DefaultDependencyFilter.java index 169573a6a..a535a5d5e 100644 --- a/common/src/main/java/net/neoforged/gradle/common/dependency/DefaultDependencyFilter.java +++ b/common/src/main/java/net/neoforged/gradle/common/dependency/DefaultDependencyFilter.java @@ -1,22 +1,15 @@ package net.neoforged.gradle.common.dependency; import net.neoforged.gradle.dsl.common.dependency.DependencyFilter; -import org.gradle.api.Project; -import org.gradle.api.artifacts.ModuleDependency; -import org.gradle.api.artifacts.ResolvedDependency; import org.gradle.api.specs.Spec; import java.util.ArrayList; import java.util.List; -public class DefaultDependencyFilter extends AbstractDependencyManagementObject implements DependencyFilter { +public abstract class DefaultDependencyFilter extends AbstractDependencyManagementObject implements DependencyFilter { protected final List> includeSpecs = new ArrayList<>(); protected final List> excludeSpecs = new ArrayList<>(); - public DefaultDependencyFilter(Project project) { - super(project); - } - @Override public DependencyFilter exclude(Spec spec) { excludeSpecs.add(spec); @@ -29,16 +22,6 @@ public DependencyFilter include(Spec spec) { return this; } - @Override - public boolean isIncluded(ResolvedDependency dependency) { - return isIncluded(createArtifactIdentifier(dependency)); - } - - @Override - public boolean isIncluded(ModuleDependency dependency) { - return isIncluded(createArtifactIdentifier(dependency)); - } - @Override public boolean isIncluded(ArtifactIdentifier dependency) { boolean include = includeSpecs.isEmpty() || includeSpecs.stream().anyMatch(spec -> spec.isSatisfiedBy(dependency)); diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/DefaultDependencyVersionInformationHandler.java b/common/src/main/java/net/neoforged/gradle/common/dependency/DefaultDependencyVersionInformationHandler.java index 168e5f7ba..df5d4c011 100644 --- a/common/src/main/java/net/neoforged/gradle/common/dependency/DefaultDependencyVersionInformationHandler.java +++ b/common/src/main/java/net/neoforged/gradle/common/dependency/DefaultDependencyVersionInformationHandler.java @@ -4,22 +4,16 @@ import net.neoforged.gradle.dsl.common.dependency.DependencyVersionInformationHandler; import org.apache.maven.artifact.versioning.ArtifactVersion; import org.apache.maven.artifact.versioning.VersionRange; -import org.gradle.api.Project; -import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.specs.Spec; import java.util.Map; import java.util.Optional; -public class DefaultDependencyVersionInformationHandler extends AbstractDependencyManagementObject implements DependencyVersionInformationHandler { +public abstract class DefaultDependencyVersionInformationHandler extends AbstractDependencyManagementObject implements DependencyVersionInformationHandler { private final Map, String> rangedVersions = Maps.newHashMap(); private final Map, String> pinnedVersions = Maps.newHashMap(); - public DefaultDependencyVersionInformationHandler(final Project project) { - super(project); - } - @Override public void ranged(final Spec spec, final String range) { rangedVersions.put(spec, range); @@ -46,8 +40,7 @@ public void pin(final Spec spec, final ArtifactVersi } @Override - public Optional getVersionRange(final ModuleDependency dependency) { - final ArtifactIdentifier identifier = createArtifactIdentifier(dependency); + public Optional getVersionRange(final ArtifactIdentifier identifier) { return rangedVersions.entrySet().stream() .filter(entry -> entry.getKey().isSatisfiedBy(identifier)) .map(Map.Entry::getValue) @@ -55,8 +48,7 @@ public Optional getVersionRange(final ModuleDependency dependency) { } @Override - public Optional getVersion(final ModuleDependency dependency) { - final ArtifactIdentifier identifier = createArtifactIdentifier(dependency); + public Optional getVersion(final ArtifactIdentifier identifier) { return pinnedVersions.entrySet().stream() .filter(entry -> entry.getKey().isSatisfiedBy(identifier)) .map(Map.Entry::getValue) diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/JarJarArtifacts.java b/common/src/main/java/net/neoforged/gradle/common/dependency/JarJarArtifacts.java new file mode 100644 index 000000000..a99085871 --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/dependency/JarJarArtifacts.java @@ -0,0 +1,246 @@ +package net.neoforged.gradle.common.dependency; + +import net.neoforged.gradle.common.extensions.JarJarExtension; +import net.neoforged.gradle.dsl.common.dependency.DependencyFilter; +import net.neoforged.gradle.dsl.common.dependency.DependencyManagementObject; +import net.neoforged.gradle.dsl.common.dependency.DependencyVersionInformationHandler; +import net.neoforged.jarjar.metadata.ContainedJarIdentifier; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.component.ComponentIdentifier; +import org.gradle.api.artifacts.component.ComponentSelector; +import org.gradle.api.artifacts.component.ModuleComponentIdentifier; +import org.gradle.api.artifacts.component.ModuleComponentSelector; +import org.gradle.api.artifacts.result.*; +import org.gradle.api.artifacts.type.ArtifactTypeDefinition; +import org.gradle.api.capabilities.Capability; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.SetProperty; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Nested; +import org.jetbrains.annotations.Nullable; + +import javax.inject.Inject; +import java.util.*; +import java.util.stream.Collectors; + +public abstract class JarJarArtifacts { + private transient final SetProperty includedRootComponents; + private transient final SetProperty includedArtifacts; + + private final DependencyFilter dependencyFilter; + private final DependencyVersionInformationHandler dependencyVersionInformationHandler; + + + @Internal + protected SetProperty getIncludedRootComponents() { + return includedRootComponents; + } + + @Internal + protected SetProperty getIncludedArtifacts() { + return includedArtifacts; + } + + @Inject + protected abstract ObjectFactory getObjectFactory(); + + @Nested + public abstract ListProperty getResolvedArtifacts(); + + @Nested + public DependencyFilter getDependencyFilter() { + return dependencyFilter; + } + + @Nested + public DependencyVersionInformationHandler getDependencyVersionInformationHandler() { + return dependencyVersionInformationHandler; + } + + public JarJarArtifacts() { + dependencyFilter = getObjectFactory().newInstance(DefaultDependencyFilter.class); + dependencyVersionInformationHandler = getObjectFactory().newInstance(DefaultDependencyVersionInformationHandler.class); + includedRootComponents = getObjectFactory().setProperty(ResolvedComponentResult.class); + includedArtifacts = getObjectFactory().setProperty(ResolvedArtifactResult.class); + + includedArtifacts.finalizeValueOnRead(); + includedRootComponents.finalizeValueOnRead(); + + final DependencyFilter filter = getDependencyFilter(); + final DependencyVersionInformationHandler versionHandler = getDependencyVersionInformationHandler(); + getResolvedArtifacts().set(getIncludedRootComponents().zip(getIncludedArtifacts(), (components, artifacts) -> getIncludedJars(filter, versionHandler, components, artifacts))); + } + + public void configuration(Configuration jarJarConfiguration) { + getIncludedArtifacts().addAll(jarJarConfiguration.getIncoming().artifactView(config -> { + config.attributes( + attr -> attr.attribute(ArtifactTypeDefinition.ARTIFACT_TYPE_ATTRIBUTE, ArtifactTypeDefinition.JAR_TYPE) + ); + }).getArtifacts().getResolvedArtifacts()); + getIncludedRootComponents().add(jarJarConfiguration.getIncoming().getResolutionResult().getRootComponent()); + } + + private static List getIncludedJars(DependencyFilter filter, DependencyVersionInformationHandler versionHandler, Set rootComponents, Set artifacts) { + Map versions = new HashMap<>(); + Map versionRanges = new HashMap<>(); + Set knownIdentifiers = new HashSet<>(); + + for (ResolvedComponentResult rootComponent : rootComponents) { + collectFromComponent(rootComponent, knownIdentifiers, versions, versionRanges); + } + List data = new ArrayList<>(); + for (ResolvedArtifactResult result : artifacts) { + ResolvedVariantResult variant = result.getVariant(); + + DependencyManagementObject.ArtifactIdentifier artifactIdentifier = capabilityOrModule(variant); + if (artifactIdentifier == null) { + continue; + } + + if (!filter.isIncluded(artifactIdentifier)) { + continue; + } + ContainedJarIdentifier jarIdentifier = new ContainedJarIdentifier(artifactIdentifier.getGroup(), artifactIdentifier.getName()); + if (!knownIdentifiers.contains(jarIdentifier)) { + continue; + } + + String version = versionHandler.getVersion(artifactIdentifier).orElse(versions.get(jarIdentifier)); + if (version == null) { + version = getVersionFrom(variant); + } + + String versionRange = versionHandler.getVersionRange(artifactIdentifier).orElse(versionRanges.get(jarIdentifier)); + if (versionRange == null) { + versionRange = getVersionRangeFrom(variant); + } + if (versionRange == null) { + versionRange = makeOpenRange(variant); + } + + if (version != null && versionRange != null) { + data.add(new ResolvedJarJarArtifact(result.getFile(), version, versionRange, jarIdentifier.group(), jarIdentifier.artifact())); + } + } + return data.stream() + .sorted(Comparator.comparing(d -> d.getGroup() + ":" + d.getArtifact())) + .collect(Collectors.toList()); + } + + private static void collectFromComponent(ResolvedComponentResult rootComponent, Set knownIdentifiers, Map versions, Map versionRanges) { + for (DependencyResult result : rootComponent.getDependencies()) { + if (!(result instanceof ResolvedDependencyResult)) { + continue; + } + ResolvedDependencyResult resolvedResult = (ResolvedDependencyResult) result; + ComponentSelector requested = resolvedResult.getRequested(); + ResolvedVariantResult variant = resolvedResult.getResolvedVariant(); + + DependencyManagementObject.ArtifactIdentifier artifactIdentifier = capabilityOrModule(variant); + if (artifactIdentifier == null) { + continue; + } + + ContainedJarIdentifier jarIdentifier = new ContainedJarIdentifier(artifactIdentifier.getGroup(), artifactIdentifier.getName()); + knownIdentifiers.add(jarIdentifier); + + String versionRange = getVersionRangeFrom(variant); + if (versionRange == null && requested instanceof ModuleComponentSelector) { + ModuleComponentSelector requestedModule = (ModuleComponentSelector) requested; + if (isValidVersionRange(requestedModule.getVersionConstraint().getStrictVersion())) { + versionRange = requestedModule.getVersionConstraint().getStrictVersion(); + } else if (isValidVersionRange(requestedModule.getVersionConstraint().getRequiredVersion())) { + versionRange = requestedModule.getVersionConstraint().getRequiredVersion(); + } else if (isValidVersionRange(requestedModule.getVersionConstraint().getPreferredVersion())) { + versionRange = requestedModule.getVersionConstraint().getPreferredVersion(); + } if (isValidVersionRange(requestedModule.getVersion())) { + versionRange = requestedModule.getVersion(); + } + } + if (versionRange == null) { + versionRange = makeOpenRange(variant); + } + + String version = getVersionFrom(variant); + + if (version != null) { + versions.put(jarIdentifier, version); + } + if (versionRange != null) { + versionRanges.put(jarIdentifier, versionRange); + } + } + } + + private static @Nullable String getVersionRangeFrom(final ResolvedVariantResult variant) { + return variant.getAttributes().getAttribute(JarJarExtension.JAR_JAR_RANGE_ATTRIBUTE); + } + + private static @Nullable DependencyManagementObject.ArtifactIdentifier capabilityOrModule(final ResolvedVariantResult variant) { + DependencyManagementObject.ArtifactIdentifier moduleIdentifier = null; + if (variant.getOwner() instanceof ModuleComponentIdentifier) { + ModuleComponentIdentifier moduleComponentIdentifier = (ModuleComponentIdentifier) variant.getOwner(); + moduleIdentifier = new DependencyManagementObject.ArtifactIdentifier( + moduleComponentIdentifier.getGroup(), + moduleComponentIdentifier.getModule(), + moduleComponentIdentifier.getVersion() + ); + } + + List capabilityIdentifiers = variant.getCapabilities().stream() + .map(capability -> new DependencyManagementObject.ArtifactIdentifier( + capability.getGroup(), + capability.getName(), + capability.getVersion() + )) + .collect(Collectors.toList()); + + if (moduleIdentifier != null && capabilityIdentifiers.contains(moduleIdentifier)) { + return moduleIdentifier; + } else if (capabilityIdentifiers.isEmpty()) { + return null; + } + return capabilityIdentifiers.get(0); + } + + private static @Nullable String moduleOrCapabilityVersion(final ResolvedVariantResult variant) { + DependencyManagementObject.@Nullable ArtifactIdentifier identifier = capabilityOrModule(variant); + if (identifier != null) { + return identifier.getVersion(); + } + return null; + } + + private static @Nullable String makeOpenRange(final ResolvedVariantResult variant) { + String baseVersion = moduleOrCapabilityVersion(variant); + + if (baseVersion == null) { + return null; + } + + return "[" + baseVersion + ",)"; + } + + private static @Nullable String getVersionFrom(final ResolvedVariantResult variant) { + String version = variant.getAttributes().getAttribute(JarJarExtension.FIXED_JAR_JAR_VERSION_ATTRIBUTE); + if (version == null) { + version = moduleOrCapabilityVersion(variant); + } + return version; + } + + private static boolean isValidVersionRange(final @Nullable String range) { + if (range == null) { + return false; + } + try { + final VersionRange data = VersionRange.createFromVersionSpec(range); + return data.hasRestrictions() && data.getRecommendedVersion() == null && !range.contains("+"); + } catch (InvalidVersionSpecificationException e) { + return false; + } + } +} diff --git a/common/src/main/java/net/neoforged/gradle/common/dependency/ResolvedJarJarArtifact.java b/common/src/main/java/net/neoforged/gradle/common/dependency/ResolvedJarJarArtifact.java new file mode 100644 index 000000000..adcb5e784 --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/dependency/ResolvedJarJarArtifact.java @@ -0,0 +1,88 @@ +package net.neoforged.gradle.common.dependency; + +import net.neoforged.jarjar.metadata.ContainedJarIdentifier; +import net.neoforged.jarjar.metadata.ContainedJarMetadata; +import net.neoforged.jarjar.metadata.ContainedVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; + +import java.io.File; +import java.io.IOException; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class ResolvedJarJarArtifact { + + private final File file; + private final String version; + private final String versionRange; + private final String group; + private final String artifact; + + public ResolvedJarJarArtifact(File file, String version, String versionRange, String group, String artifact) { + this.file = file; + this.version = version; + this.versionRange = versionRange; + this.group = group; + this.artifact = artifact; + } + + public ContainedJarIdentifier createContainedJarIdentifier() { + return new ContainedJarIdentifier(group, artifact); + } + + public ContainedVersion createContainedVersion() { + try { + return new ContainedVersion( + VersionRange.createFromVersionSpec(versionRange), + new DefaultArtifactVersion(version) + ); + } catch (InvalidVersionSpecificationException e) { + throw new RuntimeException(e); + } + } + + public ContainedJarMetadata createContainerMetadata() { + return new ContainedJarMetadata(createContainedJarIdentifier(), createContainedVersion(), "META-INF/jarjar/"+file.getName(), isObfuscated(file)); + } + + @InputFile + @PathSensitive(PathSensitivity.NAME_ONLY) + public File getFile() { + return file; + } + + @Input + public String getVersion() { + return version; + } + + @Input + public String getVersionRange() { + return versionRange; + } + + @Input + public String getGroup() { + return group; + } + + @Input + public String getArtifact() { + return artifact; + } + + private static boolean isObfuscated(final File dependency) { + try(final JarFile jarFile = new JarFile(dependency)) { + final Manifest manifest = jarFile.getManifest(); + return manifest.getMainAttributes().containsKey("Obfuscated-By"); + } catch (IOException e) { + throw new RuntimeException("Could not read jar file for dependency", e); + } + } +} diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/DefaultJarJarFeature.java b/common/src/main/java/net/neoforged/gradle/common/extensions/DefaultJarJarFeature.java new file mode 100644 index 000000000..75a9a4231 --- /dev/null +++ b/common/src/main/java/net/neoforged/gradle/common/extensions/DefaultJarJarFeature.java @@ -0,0 +1,165 @@ +package net.neoforged.gradle.common.extensions; + +import net.neoforged.gradle.common.tasks.JarJar; +import net.neoforged.gradle.dsl.common.extensions.JarJarFeature; +import org.gradle.api.Project; +import org.gradle.api.artifacts.Configuration; +import org.gradle.api.artifacts.PublishArtifact; +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.attributes.java.TargetJvmVersion; +import org.gradle.api.plugins.JavaPlugin; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.api.tasks.bundling.Jar; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.language.base.plugins.LifecycleBasePlugin; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class DefaultJarJarFeature implements JarJarFeature { + public static final String JAR_JAR_TASK_NAME = "jarJar"; + public static final String JAR_JAR_GROUP = "jarjar"; + public static final String JAR_JAR_DEFAULT_CONFIGURATION_NAME = "jarJar"; + + protected final Project project; + protected final String prefix; + private boolean disabled; + private boolean enabled; + private boolean disableDefaultSources; + private PublishArtifact addedToPublication; + private final List removedFromPublication = new ArrayList<>(); + + @Inject + public DefaultJarJarFeature(final Project project, final String prefix) { + this.project = project; + this.prefix = prefix; + } + + @Override + public void enable() { + if (!this.disabled) + enable(true); + } + + private void enable(boolean enabled) { + if (this.enabled == enabled) { + return; + } + this.enabled = enabled; + final JarJar task = (JarJar) project.getTasks().findByPath(withPrefix("jarJar")); + Configuration runtimeElements = project.getConfigurations().findByName(withPrefix((JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME))); + if (task != null) { + if (runtimeElements != null) { + if (enabled) { + removedFromPublication.clear(); + removedFromPublication.addAll(runtimeElements.getArtifacts()); + runtimeElements.getArtifacts().clear(); + project.artifacts(handler -> + addedToPublication = handler.add(withPrefix(JavaPlugin.RUNTIME_ELEMENTS_CONFIGURATION_NAME), task, artifact -> + artifact.builtBy(task) + ) + ); + } else { + runtimeElements.getArtifacts().remove(addedToPublication); + runtimeElements.getArtifacts().addAll(removedFromPublication); + } + } + if (!task.getEnabled() == enabled) { + task.setEnabled(enabled); + } + } + } + + @Override + public void disable() { + disable(true); + } + + @Override + public void disable(boolean disable) { + this.disabled = disable; + if (disable) { + enable(false); + } + } + + @Override + public boolean getDefaultSourcesDisabled() { + return this.disableDefaultSources; + } + + @Override + public void disableDefaultSources() { + disableDefaultSources(true); + } + + @Override + public void disableDefaultSources(boolean value) { + this.disableDefaultSources = value; + } + + protected String withPrefix(String name) { + if (this.prefix.isEmpty()) { + return name; + } else { + return this.prefix + name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1); + } + } + + + public void createTaskAndConfiguration() { + final Configuration configuration = project.getConfigurations().create(withPrefix(JAR_JAR_DEFAULT_CONFIGURATION_NAME)); + configuration.setTransitive(false); + configuration.getAllDependencies().configureEach(dep -> + this.enable() + ); + // jarJar configurations should be resolvable, but ought not to be exposed to consumers; + // as it has attributes, it could conflict with normal exposed configurations + configuration.setCanBeResolved(true); + configuration.setCanBeConsumed(false); + + JavaPluginExtension javaPlugin = project.getExtensions().getByType(JavaPluginExtension.class); + + configuration.attributes(attributes -> { + // Unfortunately, while we can hopefully rely on disambiguation rules to get us some of these, others run + // into issues. The target JVM version is the most worrying - we don't want to pull in a variant for a newer + // jvm version. We could copy DefaultJvmFeature, and search for the target version of the compile task, + // but this is difficult - we only have a feature name, not the linked source set. For this reason, we use + // the toolchain version, which is the most likely to be correct. + attributes.attributeProvider(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, javaPlugin.getToolchain().getLanguageVersion().map(JavaLanguageVersion::asInt)); + attributes.attribute(Usage.USAGE_ATTRIBUTE, project.getObjects().named(Usage.class, Usage.JAVA_RUNTIME)); + attributes.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, project.getObjects().named(LibraryElements.class, LibraryElements.JAR)); + attributes.attribute(Category.CATEGORY_ATTRIBUTE, project.getObjects().named(Category.class, Category.LIBRARY)); + attributes.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.EXTERNAL)); + }); + + TaskProvider jarJarTask = project.getTasks().register(withPrefix(JAR_JAR_TASK_NAME), net.neoforged.gradle.common.tasks.JarJar.class, jarJar -> { + jarJar.setGroup(JAR_JAR_GROUP); + jarJar.setDescription("Create a combined JAR of project and selected dependencies"); + jarJar.getArchiveClassifier().convention(prefix.isEmpty() ? "all" : prefix + "-all"); + + if (!this.getDefaultSourcesDisabled()) { + Jar jarTask = (Jar) project.getTasks().getByName(withPrefix(JavaPlugin.JAR_TASK_NAME)); + jarJar.dependsOn(jarTask); + jarJar.getManifest().inheritFrom(jarTask.getManifest()); + jarJar.from(project.zipTree(jarTask.getArchiveFile()).matching(patternFilterable -> { + patternFilterable.exclude("META-INF/MANIFEST.MF"); + })); + } + + jarJar.configuration(configuration); + + jarJar.setEnabled(false); + }); + + project.getTasks().named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME, t -> { + t.dependsOn(jarJarTask); + }); + } +} diff --git a/common/src/main/java/net/neoforged/gradle/common/extensions/JarJarExtension.java b/common/src/main/java/net/neoforged/gradle/common/extensions/JarJarExtension.java index a13e97740..f6cf7bab6 100644 --- a/common/src/main/java/net/neoforged/gradle/common/extensions/JarJarExtension.java +++ b/common/src/main/java/net/neoforged/gradle/common/extensions/JarJarExtension.java @@ -1,174 +1,55 @@ package net.neoforged.gradle.common.extensions; -import net.neoforged.gradle.dsl.common.dependency.DependencyFilter; -import net.neoforged.gradle.dsl.common.dependency.DependencyVersionInformationHandler; -import net.neoforged.gradle.common.tasks.JarJar; -import org.gradle.api.Action; +import net.neoforged.gradle.dsl.common.extensions.JarJarFeature; import org.gradle.api.Project; -import org.gradle.api.Task; import org.gradle.api.artifacts.Dependency; import org.gradle.api.artifacts.ModuleDependency; import org.gradle.api.attributes.Attribute; -import org.gradle.api.publish.maven.MavenPublication; import javax.inject.Inject; -import java.util.Optional; +import java.util.HashMap; +import java.util.Map; -public class JarJarExtension implements net.neoforged.gradle.dsl.common.extensions.JarJar { +public class JarJarExtension extends DefaultJarJarFeature implements net.neoforged.gradle.dsl.common.extensions.JarJar { + public static final Attribute JAR_JAR_RANGE_ATTRIBUTE = Attribute.of("jarJarRange", String.class); + public static final Attribute FIXED_JAR_JAR_VERSION_ATTRIBUTE = Attribute.of("fixedJarJarVersion", String.class); - private final Attribute fixedJarJarVersionAttribute = Attribute.of("fixedJarJarVersion", String.class); - private final Attribute jarJarRangeAttribute = Attribute.of("jarJarRange", String.class); - - private final Project project; - private boolean disabled; - private boolean disableDefaultSources; + private final Map features = new HashMap<>(); @Inject public JarJarExtension(final Project project) { - this.project = project; - } - - @Override - public void enable() { - if (!this.disabled) - enable(true); - } - - private void enable(boolean enabled) { - final Task task = project.getTasks().findByPath("jarJar"); - if (task != null) { - task.setEnabled(enabled); - } - } - - @Override - public void disable() { - disable(true); - } - - @Override - public void disable(boolean disable) { - this.disabled = disable; - if (disable) { - enable(false); - } - } - - @Override - public boolean getDefaultSourcesDisabled() { - return this.disableDefaultSources; - } - - @Override - public void disableDefaultSources() { - disableDefaultSources(true); - } - - @Override - public void disableDefaultSources(boolean value) { - this.disableDefaultSources = value; - } - - @Override - public void fromRuntimeConfiguration() { - enable(); - project.getTasks().withType(JarJar.class).configureEach(JarJar::fromRuntimeConfiguration); + super(project, ""); + features.put("", this); } @Override + @Deprecated public void pin(Dependency dependency, String version) { enable(); if (dependency instanceof ModuleDependency) { final ModuleDependency moduleDependency = (ModuleDependency) dependency; - moduleDependency.attributes(attributeContainer -> attributeContainer.attribute(fixedJarJarVersionAttribute, version)); - } - } - - @Override - public Optional getPin(Dependency dependency) { - if (dependency instanceof ModuleDependency) { - final ModuleDependency moduleDependency = (ModuleDependency) dependency; - return Optional.ofNullable(moduleDependency.getAttributes().getAttribute(fixedJarJarVersionAttribute)); + moduleDependency.attributes(attributeContainer -> attributeContainer.attribute(FIXED_JAR_JAR_VERSION_ATTRIBUTE, version)); } - return Optional.empty(); } @Override + @Deprecated public void ranged(Dependency dependency, String range) { enable(); if (dependency instanceof ModuleDependency) { final ModuleDependency moduleDependency = (ModuleDependency) dependency; - moduleDependency.attributes(attributeContainer -> attributeContainer.attribute(jarJarRangeAttribute, range)); - } - } - - @Override - public Optional getRange(Dependency dependency) { - if (dependency instanceof ModuleDependency) { - final ModuleDependency moduleDependency = (ModuleDependency) dependency; - return Optional.ofNullable(moduleDependency.getAttributes().getAttribute(jarJarRangeAttribute)); + moduleDependency.attributes(attributeContainer -> attributeContainer.attribute(JAR_JAR_RANGE_ATTRIBUTE, range)); } - return Optional.empty(); - } - - @Override - public JarJarExtension dependencies(Action c) { - enable(); - project.getTasks().withType(JarJar.class).configureEach(jarJar -> jarJar.dependencies(c)); - return this; - } - - @Override - public JarJarExtension versionInformation(Action c) { - enable(); - project.getTasks().withType(JarJar.class).configureEach(jarJar -> jarJar.versionInformation(c)); - return this; - } - - @Override - public MavenPublication component(MavenPublication mavenPublication) { - return component(mavenPublication, true); - } - - public MavenPublication component(MavenPublication mavenPublication, boolean handleDependencies) { - enable(); - project.getTasks().withType(JarJar.class).configureEach(task -> component(mavenPublication, task, false, handleDependencies)); - - return mavenPublication; - } - - public MavenPublication component(MavenPublication mavenPublication, JarJar task) { - enable(); - return component(mavenPublication, task, true, true); - } - - public MavenPublication cleanedComponent(MavenPublication mavenPublication, JarJar task, boolean handleDependencies) { - enable(); - return component(mavenPublication, task, true, handleDependencies); } - private MavenPublication component(MavenPublication mavenPublication, JarJar task, boolean handleCleaning) { - return component(mavenPublication, task, handleCleaning, true); - } - - private MavenPublication component(MavenPublication mavenPublication, JarJar task, boolean handleCleaning, boolean handleDependencies) { - if (!task.isEnabled()) { - return mavenPublication; - } - - if (handleCleaning) { - //TODO: Handle this gracefully somehow? + public JarJarFeature forFeature(String featureName) { + if (featureName == null || featureName.isEmpty()) { + return this; } - - mavenPublication.artifact(task, mavenArtifact -> { - mavenArtifact.setClassifier(task.getArchiveClassifier().get()); - mavenArtifact.setExtension(task.getArchiveExtension().get()); + return features.computeIfAbsent(featureName, f -> { + DefaultJarJarFeature feature = new DefaultJarJarFeature(project, f); + feature.createTaskAndConfiguration(); + return feature; }); - - if (handleDependencies) { - //TODO: Handle this gracefully. - } - - return mavenPublication; } } diff --git a/common/src/main/java/net/neoforged/gradle/common/tasks/JarJar.java b/common/src/main/java/net/neoforged/gradle/common/tasks/JarJar.java index 0abce95f8..0325d276a 100644 --- a/common/src/main/java/net/neoforged/gradle/common/tasks/JarJar.java +++ b/common/src/main/java/net/neoforged/gradle/common/tasks/JarJar.java @@ -1,274 +1,114 @@ package net.neoforged.gradle.common.tasks; -import net.neoforged.jarjar.metadata.*; -import net.neoforged.gradle.dsl.common.dependency.DependencyFilter; -import net.neoforged.gradle.dsl.common.dependency.DependencyVersionInformationHandler; -import net.neoforged.gradle.common.dependency.DefaultDependencyFilter; -import net.neoforged.gradle.common.dependency.DefaultDependencyVersionInformationHandler; +import net.neoforged.gradle.common.dependency.JarJarArtifacts; +import net.neoforged.gradle.common.dependency.ResolvedJarJarArtifact; import net.neoforged.gradle.common.manifest.DefaultInheritManifest; import net.neoforged.gradle.common.manifest.InheritManifest; -import org.apache.maven.artifact.versioning.DefaultArtifactVersion; -import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; -import org.apache.maven.artifact.versioning.VersionRange; +import net.neoforged.gradle.dsl.common.dependency.DependencyFilter; +import net.neoforged.gradle.dsl.common.dependency.DependencyVersionInformationHandler; +import net.neoforged.jarjar.metadata.Metadata; +import net.neoforged.jarjar.metadata.MetadataIOHandler; import org.gradle.api.Action; -import org.gradle.api.artifacts.*; -import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.artifacts.Configuration; import org.gradle.api.file.CopySpec; import org.gradle.api.file.DuplicatesStrategy; -import org.gradle.api.file.FileCollection; import org.gradle.api.internal.file.FileResolver; -import org.gradle.api.tasks.*; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.TaskAction; import org.gradle.api.tasks.bundling.Jar; -import javax.annotation.Nullable; -import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; -import java.util.Optional; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.jar.JarFile; -import java.util.jar.Manifest; +import java.util.List; import java.util.stream.Collectors; -@SuppressWarnings("unused") public abstract class JarJar extends Jar { - private final List configurations; - private transient DependencyFilter dependencyFilter; - private transient DependencyVersionInformationHandler dependencyVersionInformationHandler; - - private FileCollection sourceSetsClassesDirs; - private final ConfigurableFileCollection includedDependencies = getProject().files((Callable) () -> getProject().files( - getResolvedDependencies().stream().flatMap(d -> d.getAllModuleArtifacts().stream()).map(ResolvedArtifact::getFile).toArray() - )); + @Nested + public abstract JarJarArtifacts getJarJarArtifacts(); - private final ConfigurableFileCollection metadata = getProject().files((Callable) () -> { - writeMetadata(); - return getProject().files(getJarJarMetadataPath().toFile()); - }); + @Override + public InheritManifest getManifest() { + return (InheritManifest) super.getManifest(); + } private final CopySpec jarJarCopySpec; public JarJar() { - super(); - setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE); //As opposed to shadow, we do not filter out our entries early!, So we need to handle them accordingly. - dependencyFilter = new DefaultDependencyFilter(getProject()); - dependencyVersionInformationHandler = new DefaultDependencyVersionInformationHandler(getProject()); - setManifest(new DefaultInheritManifest(getServices().get(FileResolver.class))); - configurations = new ArrayList<>(); - this.jarJarCopySpec = this.getMainSpec().addChild(); this.jarJarCopySpec.into("META-INF/jarjar"); - } - - @InputFiles - @PathSensitive(PathSensitivity.NONE) - FileCollection getSourceSetsClassesDirs() { - if (sourceSetsClassesDirs == null) { - ConfigurableFileCollection allClassesDirs = getProject().getObjects().fileCollection(); - sourceSetsClassesDirs = allClassesDirs.filter(File::isDirectory); - } - return sourceSetsClassesDirs; - } - - @Override - public InheritManifest getManifest() { - return (InheritManifest) super.getManifest(); - } - - @TaskAction - protected void copy() { - this.jarJarCopySpec.from(getIncludedDependencies()); - if (!createMetadata().jars().isEmpty()) { - // Only copy metadata if not empty. - this.jarJarCopySpec.from(getJarJarMetadataPath().toFile()); - } - super.copy(); - } - @Classpath - public FileCollection getIncludedDependencies() { - return includedDependencies; + setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE); //As opposed to shadow, we do not filter out our entries early!, So we need to handle them accordingly. + setManifest(new DefaultInheritManifest(getServices().get(FileResolver.class))); } - @Internal - public Set getResolvedDependencies() { - return this.configurations.stream().flatMap(config -> config.getAllDependencies().stream()) - .filter(ModuleDependency.class::isInstance) - .map(ModuleDependency.class::cast) - .map(this::getResolvedDependency) - .filter(this.dependencyFilter::isIncluded) - .collect(Collectors.toSet()); + @Nested + public DependencyFilter getDependencyFilter() { + return getJarJarArtifacts().getDependencyFilter(); } - @Classpath - public FileCollection getMetadata() { - return metadata; + @Nested + public DependencyVersionInformationHandler getDependencyVersionInformationHandler() { + return getJarJarArtifacts().getDependencyVersionInformationHandler(); } public JarJar dependencies(Action c) { - c.execute(dependencyFilter); + c.execute(getDependencyFilter()); return this; } public JarJar versionInformation(Action c) { - c.execute(dependencyVersionInformationHandler); + c.execute(getDependencyVersionInformationHandler()); return this; } - @Classpath - @org.gradle.api.tasks.Optional - public List getConfigurations() { - return this.configurations; - } - - public void setConfigurations(List configurations) { - this.configurations.clear(); - this.configurations.addAll(configurations); - } - - @Internal - public DependencyFilter getDependencyFilter() { - return this.dependencyFilter; - } - - public void setDependencyFilter(DependencyFilter filter) { - this.dependencyFilter = filter; - } - - public void configuration(@Nullable final Configuration configuration) { - if (configuration == null) { - return; - } - - this.configurations.add(configuration); - } - - public void fromRuntimeConfiguration() { - final Configuration runtimeConfiguration = getProject().getConfigurations().findByName("runtimeClasspath"); - if (runtimeConfiguration != null) { - this.configuration(runtimeConfiguration); + @TaskAction + @Override + protected void copy() { + List includedJars = getJarJarArtifacts().getResolvedArtifacts().get(); + this.jarJarCopySpec.from( + includedJars.stream().map(ResolvedJarJarArtifact::getFile).collect(Collectors.toList()) + ); + if (!writeMetadata(includedJars).jars().isEmpty()) { + // Only copy metadata if not empty. + this.jarJarCopySpec.from(getJarJarMetadataPath().toFile()); } + super.copy(); } @SuppressWarnings("ResultOfMethodCallIgnored") - private void writeMetadata() { + private Metadata writeMetadata(List includedJars) { final Path metadataPath = getJarJarMetadataPath(); - - try { - metadataPath.toFile().getParentFile().mkdirs(); - Files.deleteIfExists(metadataPath); - Files.write(metadataPath, MetadataIOHandler.toLines(createMetadata()), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); - } catch (IOException e) { - throw new RuntimeException("Failed to write JarJar dependency metadata to disk.", e); + final Metadata metadata = createMetadata(includedJars); + + if (!metadata.jars().isEmpty()) { + try { + metadataPath.toFile().getParentFile().mkdirs(); + Files.deleteIfExists(metadataPath); + Files.write(metadataPath, MetadataIOHandler.toLines(metadata), StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE); + } catch (IOException e) { + throw new RuntimeException("Failed to write JarJar dependency metadata to disk.", e); + } } + return metadata; + } + + public void configuration(Configuration jarJarConfiguration) { + getJarJarArtifacts().configuration(jarJarConfiguration); + dependsOn(jarJarConfiguration); } private Path getJarJarMetadataPath() { - return getProject().getBuildDir().toPath().resolve("jarjar").resolve(getName()).resolve("metadata.json"); + return getTemporaryDir().toPath().resolve("metadata.json"); } - private Metadata createMetadata() { + private Metadata createMetadata(List jars) { return new Metadata( - this.configurations.stream().flatMap(config -> config.getDependencies().stream()) - .filter(ModuleDependency.class::isInstance) - .map(ModuleDependency.class::cast) - .map(this::createDependencyMetadata) - .filter(Optional::isPresent) - .map(Optional::get) + jars.stream() + .map(ResolvedJarJarArtifact::createContainerMetadata) .collect(Collectors.toList()) ); } - - private Optional createDependencyMetadata(final ModuleDependency dependency) { - if (!dependencyFilter.isIncluded(dependency)) { - return Optional.empty(); - } - - if (!isValidVersionRange(Objects.requireNonNull(getVersionRangeFrom(dependency)))) { - throw createInvalidVersionRangeException(dependency, null); - } - - final ResolvedDependency resolvedDependency = getResolvedDependency(dependency); - if (!dependencyFilter.isIncluded(resolvedDependency)) { - //Skipping this file since the dependency filter does not want this to be included at all! - return Optional.empty(); - } - - try { - return Optional.of(new ContainedJarMetadata( - new ContainedJarIdentifier(dependency.getGroup(), dependency.getName()), - new ContainedVersion( - VersionRange.createFromVersionSpec(getVersionRangeFrom(dependency)), - new DefaultArtifactVersion(resolvedDependency.getModuleVersion()) - ), - "META-INF/jarjar/" + resolvedDependency.getAllModuleArtifacts().iterator().next().getFile().getName(), - isObfuscated(resolvedDependency.getAllModuleArtifacts().iterator().next().getFile()) - )); - } catch (InvalidVersionSpecificationException e) { - throw createInvalidVersionRangeException(dependency, e); - } - } - - private RuntimeException createInvalidVersionRangeException(final ModuleDependency dependency, final Throwable cause) { - return new RuntimeException("The given version specification is invalid: " + getVersionRangeFrom(dependency) - + ". If you used gradle based range versioning like 2.+, convert this to a maven compatible format: [2.0,3.0).", cause); - } - - private String getVersionRangeFrom(final ModuleDependency dependency) { - final Optional versionRange = dependencyVersionInformationHandler.getVersionRange(dependency) - ; - if (versionRange.isPresent()) { - return versionRange.get(); - } - final Optional attributeVersion = getProject().getExtensions().getByType(net.neoforged.gradle.dsl.common.extensions.JarJar.class).getRange(dependency); - - return attributeVersion.orElseGet(() -> Objects.requireNonNull(dependency.getVersion())); - } - - private String getVersionFrom(final ModuleDependency dependency) { - final Optional version = dependencyVersionInformationHandler.getVersion(dependency); - if (version.isPresent()) { - return version.get(); - } - final Optional attributeVersion = getProject().getExtensions().getByType(net.neoforged.gradle.dsl.common.extensions.JarJar.class).getPin(dependency); - - return attributeVersion.orElseGet(() -> Objects.requireNonNull(dependency.getVersion())); - } - - private ResolvedDependency getResolvedDependency(final ModuleDependency dependency) { - ModuleDependency toResolve = dependency.copy(); - if (toResolve instanceof ExternalModuleDependency) { - final ExternalModuleDependency externalDependency = (ExternalModuleDependency) toResolve; - externalDependency.version(constraint -> constraint.strictly(getVersionFrom(dependency))); - } - - final Set deps = getProject().getConfigurations().detachedConfiguration(toResolve).getResolvedConfiguration().getFirstLevelModuleDependencies(); - if (deps.isEmpty()) { - throw new IllegalArgumentException(String.format("Failed to resolve: %s", toResolve)); - } - - return deps.iterator().next(); - } - - private boolean isObfuscated(final File dependency) { - try(final JarFile jarFile = new JarFile(dependency)) { - final Manifest manifest = jarFile.getManifest(); - return manifest.getMainAttributes().containsKey("Obfuscated-By"); - } catch (IOException e) { - return false; - } - } - - private boolean isValidVersionRange(final String range) { - try { - final VersionRange data = VersionRange.createFromVersionSpec(range); - return data.hasRestrictions() && data.getRecommendedVersion() == null && !range.contains("+"); - } catch (InvalidVersionSpecificationException e) { - return false; - } - } } diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyFilter.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyFilter.groovy index 5571387a5..1ee147c4e 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyFilter.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyFilter.groovy @@ -1,8 +1,6 @@ package net.neoforged.gradle.dsl.common.dependency import groovy.transform.CompileStatic -import org.gradle.api.artifacts.ModuleDependency -import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.specs.Spec @CompileStatic @@ -27,26 +25,10 @@ interface DependencyFilter extends DependencyManagementObject { DependencyFilter include(Spec spec); /** - * Indicates if the given resolved dependency passes the filter. + * Indicates if the given identifier passes the filter. * * @param dependency The resolved dependency to check. * @return The result of the filter. */ - boolean isIncluded(ResolvedDependency dependency); - - /** - * Indicates if the given dependency passes the filter. - * - * @param dependency The dependency to check. - * @return The result of the filter. - */ - boolean isIncluded(ModuleDependency dependency); - - /** - * Checks if the given artifact identifier matches the dependency. - * - * @param identifier The identifier to check. - * @return The result of the filter. - */ - boolean isIncluded(ArtifactIdentifier identifier); + boolean isIncluded(ArtifactIdentifier dependency); } diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyManagementObject.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyManagementObject.groovy index 33e356d42..b466b403c 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyManagementObject.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyManagementObject.groovy @@ -1,19 +1,42 @@ package net.neoforged.gradle.dsl.common.dependency import groovy.transform.CompileStatic +import groovy.transform.EqualsAndHashCode +import org.gradle.api.Project import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.ExternalModuleDependency +import org.gradle.api.artifacts.FileCollectionDependency +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.file.FileCollection import org.gradle.api.specs.Spec +import javax.annotation.Nullable + @CompileStatic interface DependencyManagementObject { -/** - * Create a spec that matches dependencies using the provided notation on group, name, and version - * If a regex is supplied for any of the group, name, or version, the spec will match if the dependency matches the regex. - * - * @param notation The dependency notation to parse. - * @return The spec that matches the dependency notation. - */ - Spec dependency(Object notation); + /** + * Create a spec that matches dependencies using the provided notation on group, name, and version + * + * @param notation The dependency notation to parse. + * @return The spec that matches the dependency notation. + */ + Spec dependency(CharSequence dependencyNotation); + + /** + * Create a spec that matches dependencies on the provided group, name, and version + * + * @param notation The dependency notation to parse. + * @return The spec that matches the dependency notation. + */ + Spec dependency(@Nullable String group, String name, @Nullable String version); + + /** + * Create a spec that matches dependencies using the provided project's group, name, and version + * + * @param notation The dependency notation to parse. + * @return The spec that matches the dependency notation. + */ + Spec dependency(Project project); /** * Create a spec that matches the provided dependency on group, name, and version @@ -35,10 +58,11 @@ interface DependencyManagementObject { * Simple artifact identifier class which only references group, name and version. */ @CompileStatic + @EqualsAndHashCode(includeFields = true) final class ArtifactIdentifier { - private final String group; - private final String name; - private final String version; + private final String group + private final String name + private final String version /** * Creates a new instance of the given artifact details. @@ -47,10 +71,10 @@ interface DependencyManagementObject { * @param name The name of the artifact to identify. * @param version The version of the artifact to identify. */ - public ArtifactIdentifier(String group, String name, String version) { - this.group = group; - this.name = name; - this.version = version; + ArtifactIdentifier(String group, String name, String version) { + this.group = group + this.name = name + this.version = version } /** @@ -58,8 +82,8 @@ interface DependencyManagementObject { * * @return The group of the artifact. */ - public String getGroup() { - return group; + String getGroup() { + return group } /** @@ -67,8 +91,8 @@ interface DependencyManagementObject { * * @return The name of the artifact. */ - public String getName() { - return name; + String getName() { + return name } /** @@ -76,8 +100,8 @@ interface DependencyManagementObject { * * @return The version of the artifact. */ - public String getVersion() { - return version; + String getVersion() { + return version } } } diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyVersionInformationHandler.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyVersionInformationHandler.groovy index c5cdd9909..cc138dedc 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyVersionInformationHandler.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/dependency/DependencyVersionInformationHandler.groovy @@ -3,7 +3,6 @@ package net.neoforged.gradle.dsl.common.dependency import groovy.transform.CompileStatic import org.apache.maven.artifact.versioning.ArtifactVersion import org.apache.maven.artifact.versioning.VersionRange -import org.gradle.api.artifacts.ModuleDependency import org.gradle.api.specs.Spec @CompileStatic @@ -55,7 +54,7 @@ interface DependencyVersionInformationHandler extends DependencyManagementObject * @param dependency The dependency to get the version range for. * @return The version range, if any. */ - Optional getVersionRange(ModuleDependency dependency); + Optional getVersionRange(ArtifactIdentifier dependency); /** * Gets the version for the given dependency. @@ -63,5 +62,5 @@ interface DependencyVersionInformationHandler extends DependencyManagementObject * @param dependency The dependency to get the version for. * @return The version, if any. */ - Optional getVersion(ModuleDependency dependency); + Optional getVersion(ArtifactIdentifier dependency); } diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/JarJar.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/JarJar.groovy index 8b9e62c8a..3d619bbb1 100644 --- a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/JarJar.groovy +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/JarJar.groovy @@ -1,41 +1,40 @@ package net.neoforged.gradle.dsl.common.extensions -import net.neoforged.gradle.dsl.common.dependency.DependencyFilter -import net.neoforged.gradle.dsl.common.dependency.DependencyVersionInformationHandler import org.gradle.api.Action import org.gradle.api.artifacts.Dependency -import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.plugins.JavaPluginExtension -interface JarJar { +interface JarJar extends JarJarFeature { String EXTENSION_NAME = "jarJar"; - void enable(); - - void disable(); - - void disable(boolean disable); - - boolean getDefaultSourcesDisabled(); - - void disableDefaultSources(); - - void disableDefaultSources(boolean value); - - void fromRuntimeConfiguration(); - + /** + * Sets the version of a dependency contained within a jarJar jar. + * + * @param dependency the dependency to mark + * @param version the version to use within the jar. + * + * @deprecated Use gradle rich versions instead + */ + @Deprecated void pin(Dependency dependency, String version); - Optional getPin(Dependency dependency); - + /** + * Sets the version range of a dependency that jarJar should accept at runtime. + * + * @param dependency the dependency to mark + * @param range the version range to accept, in the form of a maven version range + * + * @deprecated Use gradle rich versions instead + */ + @Deprecated void ranged(Dependency dependency, String range); - Optional getRange(Dependency dependency); - - JarJar dependencies(Action c); - - JarJar versionInformation(Action c); - - MavenPublication component(MavenPublication mavenPublication); - + /** + * Configure jarJar for a library feature with the given name, as created by {@link JavaPluginExtension#registerFeature(String,Action)}. + * Creates a featureJarJar task and configuration for the feature if they are missing. + * @param featureName the name of the feature to configure + * @return the configuration for jarJar for the feature + */ + JarJarFeature forFeature(String featureName); } diff --git a/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/JarJarFeature.groovy b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/JarJarFeature.groovy new file mode 100644 index 000000000..72ca02d1e --- /dev/null +++ b/dsl/common/src/main/groovy/net/neoforged/gradle/dsl/common/extensions/JarJarFeature.groovy @@ -0,0 +1,35 @@ +package net.neoforged.gradle.dsl.common.extensions + +interface JarJarFeature { + /** + * Enable the jarJar default configuration, unless already disabled + */ + void enable(); + + /** + * Disable the jarJar default configuration + */ + void disable(); + + /** + * + * @param disable or un-disable the jarJar default configuration; allows reversing {@link #disable()}. + */ + void disable(boolean disable); + + /** + * {@return whether the jarJar task should by default copy the contents and manifest of the jar task} + */ + boolean getDefaultSourcesDisabled(); + + /** + * Stop the jarJar task from copying the contents and manifest of the jar task + */ + void disableDefaultSources(); + + /** + * Set whether the jarJar task should copy the contents and manifest of the jar task + * @param value whether to disable the default sources + */ + void disableDefaultSources(boolean value); +} \ No newline at end of file diff --git a/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/model/InstallerProfile.groovy b/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/model/InstallerProfile.groovy index f874a3f39..48e0fffc9 100644 --- a/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/model/InstallerProfile.groovy +++ b/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/model/InstallerProfile.groovy @@ -19,6 +19,7 @@ import org.gradle.api.Action import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.artifacts.Dependency +import org.gradle.api.artifacts.repositories.MavenArtifactRepository import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty @@ -33,6 +34,7 @@ import javax.inject.Inject import java.lang.reflect.Type import java.util.function.BiConsumer import java.util.function.BiFunction +import java.util.stream.Collectors import static net.neoforged.gradle.dsl.common.util.PropertyUtils.deserializeBool import static net.neoforged.gradle.dsl.common.util.PropertyUtils.deserializeList @@ -190,7 +192,8 @@ abstract class InstallerProfile implements ConfigurableDSLElement project.getDependencies().create(coord) }.toArray(Dependency[]::new) final Configuration configuration = ConfigurationUtils.temporaryConfiguration(project, dependencies) - final LibraryCollector collector = new LibraryCollector(project.getObjects()) + final LibraryCollector collector = new LibraryCollector(project.getObjects(), project.getRepositories() + .withType(MavenArtifactRepository).stream().map { it.url }.collect(Collectors.toList())) configuration.getAsFileTree().visit collector return collector.getLibraries() } @@ -394,4 +397,4 @@ abstract class InstallerProfile implements ConfigurableDSLElement repositoryUrls private final List libraries = new ArrayList<>(); - LibraryCollector(ObjectFactory objectFactory) { + LibraryCollector(ObjectFactory objectFactory, List repoUrl) { super(objectFactory); this.objectFactory = objectFactory; + this.repositoryUrls = repoUrl } @Override @@ -32,7 +34,12 @@ class LibraryCollector extends ModuleIdentificationVisitor { download.getArtifact().set(artifact); final String path = group.replace(".", "/") + "/" + module + "/" + version + "/" + module + "-" + version + (classifier.isEmpty() ? "" : "-" + classifier) + "." + extension; - final String url = getMavenServerFor(path) + "/" + path; + String url = getMavenServerFor(path) + "/" + path; + int pos = 0 + while (attemptConnection(url) !== 200 && pos < repositoryUrls.size()) { + url = repositoryUrls.get(pos++).resolve(path).toString() + } + final String name = group + ":" + module + ":" + version + (classifier.isEmpty() ? "" : ":" + classifier) + "@" + extension; library.getName().set(name); @@ -48,6 +55,19 @@ class LibraryCollector extends ModuleIdentificationVisitor { libraries.add(library); } + private static int attemptConnection(String url) { + try { + final conn = (HttpURLConnection) url.toURL().openConnection() + conn.setRequestMethod('HEAD') + conn.connect() + int rc = conn.responseCode + conn.disconnect() + return rc + } catch (Exception ignored) { + return 404 + } + } + private static String getMavenServerFor(String path) { try { final URL mojangMavenUrl = new URL("https://libraries.minecraft.net/" + path); diff --git a/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/util/RepositoryCollection.java b/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/util/RepositoryCollection.java new file mode 100644 index 000000000..6cf5af213 --- /dev/null +++ b/dsl/platform/src/main/groovy/net/neoforged/gradle/dsl/platform/util/RepositoryCollection.java @@ -0,0 +1,22 @@ +package net.neoforged.gradle.dsl.platform.util; + +import org.gradle.api.artifacts.dsl.RepositoryHandler; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.provider.ListProperty; +import org.gradle.api.provider.ProviderFactory; + +import java.net.URI; + +public class RepositoryCollection { + private final ListProperty urls; + + public RepositoryCollection(ProviderFactory providers, ObjectFactory objects, RepositoryHandler handler) { + this.urls = objects.listProperty(URI.class); + handler.withType(MavenArtifactRepository.class).configureEach(repo -> urls.add(providers.provider(repo::getUrl))); + } + + public ListProperty getURLs() { + return urls; + } +} diff --git a/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java b/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java index 3fa593ef8..057e921ad 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/extensions/DynamicProjectExtension.java @@ -30,6 +30,7 @@ import net.neoforged.gradle.dsl.platform.model.InstallerProfile; import net.neoforged.gradle.dsl.platform.model.LauncherProfile; import net.neoforged.gradle.dsl.platform.model.Library; +import net.neoforged.gradle.dsl.platform.util.RepositoryCollection; import net.neoforged.gradle.dsl.userdev.configurations.UserdevProfile; import net.neoforged.gradle.neoform.NeoFormProjectPlugin; import net.neoforged.gradle.neoform.runtime.definition.NeoFormRuntimeDefinition; @@ -62,6 +63,7 @@ import org.gradle.api.file.FileTree; import org.gradle.api.file.RegularFile; import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.provider.Provider; import org.gradle.api.provider.ProviderFactory; @@ -74,6 +76,7 @@ import javax.inject.Inject; import java.io.File; +import java.net.URI; import java.nio.file.Files; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -324,13 +327,15 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re launcherProfile.getArguments().set(arguments); }); - + + final ListProperty repoCollection = new RepositoryCollection(project.getProviders(), project.getObjects(), project.getRepositories()).getURLs(); final TaskProvider createLauncherJson = project.getTasks().register("createLauncherJson", CreateLauncherJson.class, task -> { task.getProfile().set(launcherProfile); task.getLibraries().from(installerConfiguration); task.getLibraries().from(pluginLayerLibraryConfiguration); task.getLibraries().from(gameLayerLibraryConfiguration); task.getLibraries().from(moduleOnlyConfiguration); + task.getRepositoryURLs().set(repoCollection); CommonRuntimeExtension.configureCommonRuntimeTaskParameters(task, runtimeDefinition, workingDirectory); }); @@ -461,6 +466,7 @@ public void runtime(final String neoFormVersion, Directory patches, Directory re final TaskProvider createLegacyInstallerJson = project.getTasks().register("createLegacyInstallerJson", CreateLegacyInstallerJson.class, task -> { task.getProfile().set(installerProfile); task.getLibraries().from(installerLibrariesConfiguration); + task.getRepositoryURLs().set(repoCollection); task.dependsOn(signUniversalJar); diff --git a/platform/src/main/java/net/neoforged/gradle/platform/tasks/CreateLauncherJson.java b/platform/src/main/java/net/neoforged/gradle/platform/tasks/CreateLauncherJson.java index 051ded1c7..30146f45d 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/tasks/CreateLauncherJson.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/tasks/CreateLauncherJson.java @@ -7,12 +7,15 @@ import net.neoforged.gradle.dsl.platform.model.LauncherProfile; import net.neoforged.gradle.dsl.platform.util.LibraryCollector; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.*; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.net.URI; +import java.net.URL; import java.nio.file.Files; @CacheableTask @@ -32,7 +35,7 @@ public void run() { clone.getLibraries().addAll( getProviderFactory().provider(() -> { - final LibraryCollector profileFiller = new LibraryCollector(getObjectFactory()); + final LibraryCollector profileFiller = new LibraryCollector(getObjectFactory(), getRepositoryURLs().get()); getLibraries().getAsFileTree().visit(profileFiller); return profileFiller.getLibraries(); }) @@ -53,4 +56,7 @@ public void run() { @InputFiles @PathSensitive(PathSensitivity.NONE) public abstract ConfigurableFileCollection getLibraries(); + + @Input + public abstract ListProperty getRepositoryURLs(); } diff --git a/platform/src/main/java/net/neoforged/gradle/platform/tasks/CreateLegacyInstallerJson.java b/platform/src/main/java/net/neoforged/gradle/platform/tasks/CreateLegacyInstallerJson.java index 1db43be5c..d716d6c19 100644 --- a/platform/src/main/java/net/neoforged/gradle/platform/tasks/CreateLegacyInstallerJson.java +++ b/platform/src/main/java/net/neoforged/gradle/platform/tasks/CreateLegacyInstallerJson.java @@ -7,12 +7,14 @@ import net.neoforged.gradle.dsl.platform.model.InstallerProfile; import net.neoforged.gradle.dsl.platform.util.LibraryCollector; import org.gradle.api.file.ConfigurableFileCollection; +import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; import org.gradle.api.tasks.*; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.net.URI; import java.nio.file.Files; @CacheableTask @@ -32,7 +34,7 @@ public void run() { copy.getLibraries().addAll( getProviderFactory().provider(() -> { - final LibraryCollector profileFiller = new LibraryCollector(getObjectFactory()); + final LibraryCollector profileFiller = new LibraryCollector(getObjectFactory(), getRepositoryURLs().get()); getLibraries().getAsFileTree().visit(profileFiller); return profileFiller.getLibraries(); }) @@ -44,10 +46,13 @@ public void run() { } } - @Nested + @Internal public abstract Property getProfile(); @InputFiles @PathSensitive(PathSensitivity.NONE) public abstract ConfigurableFileCollection getLibraries(); + + @Input + public abstract ListProperty getRepositoryURLs(); } diff --git a/userdev/src/main/java/net/neoforged/gradle/userdev/UserDevProjectPlugin.java b/userdev/src/main/java/net/neoforged/gradle/userdev/UserDevProjectPlugin.java index 479a0e85e..7bcf247f7 100644 --- a/userdev/src/main/java/net/neoforged/gradle/userdev/UserDevProjectPlugin.java +++ b/userdev/src/main/java/net/neoforged/gradle/userdev/UserDevProjectPlugin.java @@ -1,25 +1,21 @@ package net.neoforged.gradle.userdev; +import net.neoforged.gradle.common.extensions.DefaultJarJarFeature; +import net.neoforged.gradle.common.extensions.JarJarExtension; import net.neoforged.gradle.dsl.common.extensions.JarJar; import net.neoforged.gradle.dsl.userdev.extension.UserDev; import net.neoforged.gradle.neoform.NeoFormPlugin; import net.neoforged.gradle.userdev.dependency.UserDevDependencyManager; import net.neoforged.gradle.userdev.extension.UserDevExtension; -import net.neoforged.gradle.common.extensions.JarJarExtension; import net.neoforged.gradle.userdev.runtime.extension.UserDevRuntimeExtension; import org.gradle.api.Plugin; import org.gradle.api.Project; -import org.gradle.api.artifacts.Configuration; -import org.gradle.api.plugins.JavaPluginExtension; -import org.gradle.api.tasks.TaskProvider; -import org.gradle.api.tasks.bundling.Jar; -import org.gradle.language.base.plugins.LifecycleBasePlugin; public class UserDevProjectPlugin implements Plugin { - public static final String JAR_JAR_TASK_NAME = "jarJar"; - public static final String JAR_JAR_GROUP = "jarjar"; + public static final String JAR_JAR_TASK_NAME = DefaultJarJarFeature.JAR_JAR_TASK_NAME; + public static final String JAR_JAR_GROUP = DefaultJarJarFeature.JAR_JAR_GROUP; - public static final String JAR_JAR_DEFAULT_CONFIGURATION_NAME = "jarJar"; + public static final String JAR_JAR_DEFAULT_CONFIGURATION_NAME = DefaultJarJarFeature.JAR_JAR_DEFAULT_CONFIGURATION_NAME; @Override @@ -37,29 +33,6 @@ public void apply(Project project) { } protected void configureJarJarTask(Project project, JarJar jarJarExtension) { - final Configuration configuration = project.getConfigurations().create(JAR_JAR_DEFAULT_CONFIGURATION_NAME); - - JavaPluginExtension javaPluginExtension = project.getExtensions().getByType(JavaPluginExtension.class); - - TaskProvider jarJarTask = project.getTasks().register(JAR_JAR_TASK_NAME, net.neoforged.gradle.common.tasks.JarJar.class, jarJar -> { - jarJar.setGroup(JAR_JAR_GROUP); - jarJar.setDescription("Create a combined JAR of project and selected dependencies"); - jarJar.getArchiveClassifier().convention("all"); - - if (!jarJarExtension.getDefaultSourcesDisabled()) { - jarJar.getManifest().inheritFrom(((Jar) project.getTasks().getByName("jar")).getManifest()); - jarJar.from(javaPluginExtension.getSourceSets().getByName("main").getOutput()); - } - - jarJar.configuration(configuration); - - jarJar.setEnabled(false); - }); - - project.getArtifacts().add(JAR_JAR_DEFAULT_CONFIGURATION_NAME, jarJarTask); - - project.getTasks().named(LifecycleBasePlugin.ASSEMBLE_TASK_NAME, t -> { - t.dependsOn(jarJarTask); - }); + ((DefaultJarJarFeature) jarJarExtension).createTaskAndConfiguration(); } }