Skip to content

Commit

Permalink
Merge pull request #4936 from sdedic/gradle/internals-wrapped
Browse files Browse the repository at this point in the history
#4923: access to gradle internal APIs is protected from failing the project load, just logs a notification.
  • Loading branch information
MartinBalin authored Nov 8, 2022
2 parents 04440c8 + 4d6c0e9 commit 31c6618
Show file tree
Hide file tree
Showing 2 changed files with 241 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.gradle.tooling;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import org.gradle.api.NamedDomainObjectContainer;
import org.gradle.api.Project;
import org.gradle.api.internal.plugins.PluginManagerInternal;
import org.gradle.api.internal.plugins.PluginRegistry;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.internal.provider.PropertyInternal;
import org.gradle.api.internal.provider.ProviderInternal;
import org.gradle.api.internal.provider.ValueSupplier;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.reflect.HasPublicType;
import org.gradle.plugin.use.PluginId;
import org.gradle.util.VersionNumber;
import org.netbeans.modules.gradle.tooling.NbProjectInfoBuilder.ExceptionCallable;
import org.netbeans.modules.gradle.tooling.NbProjectInfoBuilder.ValueAndType;

/**
* Adapts to various Gradle implementations. As *.internal.* interfaces may change between
* releases, operations on them may be customized by this Adapter. The adapter should be compiled
* against the Gradle distribution shipped with NetBeans, but should use reflection to access
* the relevant data if the internal API changes between versions.
*
* @author sdedic
*/
public class GradleInternalAdapter {
private static final Logger LOG = Logging.getLogger(NbProjectInfoBuilder.class);

private final Project project;
private final VersionNumber gradleVersion;
/**
* Accummulates error messages, so that just one problem is logger a given type of error.
*/
private Set<String> reportedIncompatibilities = new HashSet<>();

protected NbProjectInfoModel model;

/**
* Guards {@link #pluginManager} and {@link #registry} initialization
*/
protected boolean pluginsInitialized;
protected PluginManagerInternal pluginManager;
protected PluginRegistry registry;

public GradleInternalAdapter(Project project) {
this.project = project;
this.gradleVersion = VersionNumber.parse(project.getGradle().getGradleVersion());
}

boolean initPlugins() {
if (!pluginsInitialized) {
if (project.getPluginManager() instanceof PluginManagerInternal) {
pluginManager = (PluginManagerInternal)project.getPluginManager();
}
if (project instanceof ProjectInternal) {
registry = safeCall(() -> ((ProjectInternal)project).getServices().get(PluginRegistry.class), "plugin registry").orElse(null);
} else {
registry = null;
}
}
return pluginManager != null;
}

public void setModel(NbProjectInfoModel model) {
this.model = model;
}

protected boolean isFixedValue(String description, ValueSupplier.ExecutionTimeValue etv) {
return etv.isFixedValue();
}

public boolean isMutableType(Object potentialValue) {
if (potentialValue instanceof PropertyInternal) {
return true;
} else if ((potentialValue instanceof NamedDomainObjectContainer) && (potentialValue instanceof HasPublicType)) {
return true;
} else if (potentialValue instanceof Iterable || potentialValue instanceof Map) {
return true;
}
return false;
}

public boolean hasPluginManager() {
return initPlugins();
}

public ValueAndType findPropertyValueInternal(String propName, Object val) {
return safeCall(() -> {
if (val instanceof ProviderInternal) {
ProviderInternal provided = (ProviderInternal)val;
ValueSupplier.ExecutionTimeValue etv;
try {
etv = provided.calculateExecutionTimeValue();
} catch (RuntimeException ex) {
// probably expected, ignore
return new ValueAndType(provided.getType());
}
if (isFixedValue("property " + propName, etv)) {
return new ValueAndType(provided.getType(), etv.getFixedValue());
} else {
return new ValueAndType(provided.getType());
}
} else {
return new ValueAndType(val != null ? val.getClass() : null, val);
}
}, "property " + propName).orElse(null);
}

@SuppressWarnings("unchecked")
private static <T extends Throwable> void sneakyThrow(Throwable exception) throws T {
throw (T) exception;
}

private <T, E extends Throwable> T sinceGradleOrDefault(String version, NbProjectInfoBuilder.ExceptionCallable<T, E> c, Supplier<T> def) {
if (gradleVersion.compareTo(VersionNumber.parse(version)) >= 0) {
try {
return c.call();
} catch (RuntimeException | Error e) {
throw e;
} catch (Throwable t) {
sneakyThrow(t);
return null;
}
} else {
return def.get();
}
}

public Optional<PluginId> findPluginId(Class fc) {
if (!initPlugins()) {
return Optional.empty();
}
// with Gradle 7.1+, plugins can be better enumerated. Prior to 7.1 I can only get IDs for registry-supplied plugins.
Optional<PluginId> id = sinceGradleOrDefault("7.1", () -> safeCall(() -> (PluginId)pluginManager.findPluginIdForClass(fc).orElse(null), "plugins"), Optional::empty); // NOI18N
if (id.isPresent() || registry == null) {
return id;
}
return safeCall(() -> registry.findPluginForClass(fc).orElse(null), "plugin class " + fc.getName());
}

private void noteAndLogError(Throwable ex, String description) {
String msg = "Error inspecting " + (description == null ? "project" : description);
model.noteProblem(msg + ": " + ex.toString());
LOG.log(LogLevel.LIFECYCLE, msg, ex);
}

protected <T, E extends Throwable> Optional<T> safeCall(ExceptionCallable<T, E> sup, String description) {
try {
return Optional.ofNullable(sup.call());
} catch (RuntimeException ex) {
noteAndLogError(ex, description);
return Optional.empty();
} catch (Error ex) {
if (reportedIncompatibilities.add(ex.toString())) {
noteAndLogError(ex, description);
}
return Optional.empty();
} catch (Throwable t) {
sneakyThrow(t);
return null;
}
}

public static class Gradle76 extends GradleInternalAdapter {
private static Optional<Method> refHasValue;

public Gradle76(Project project) {
super(project);
}

@Override
protected boolean isFixedValue(String description, ValueSupplier.ExecutionTimeValue etv) {
if (refHasValue == null) {
refHasValue = safeCall(() -> ValueSupplier.ExecutionTimeValue.class.getMethod("hasFixedValue"), "Gradle 7.6+ ExecutionTimeValue");
}
if (refHasValue.isPresent()) {
return safeCall(() -> (Boolean)refHasValue.get().invoke(etv), description).orElse(false);
} else {
return false;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,7 @@
import org.gradle.api.file.ConfigurableFileCollection;
import org.gradle.api.file.DirectoryProperty;
import org.gradle.api.initialization.IncludedBuild;
import org.gradle.api.internal.plugins.PluginManagerInternal;
import org.gradle.api.internal.plugins.PluginRegistry;
import org.gradle.api.internal.project.ProjectInternal;
import org.gradle.api.internal.provider.PropertyInternal;
import org.gradle.api.internal.provider.ProviderInternal;
import org.gradle.api.internal.provider.ValueSupplier.ExecutionTimeValue;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.api.plugins.ExtensionAware;
Expand Down Expand Up @@ -183,14 +178,34 @@ class NbProjectInfoBuilder {

final Project project;
final VersionNumber gradleVersion;
final GradleInternalAdapter adapter;

public static final class ValueAndType {
final Class type;
final Optional<Object> value;

public ValueAndType(Class type, Object value) {
this.type = type;
this.value = Optional.of(value);
}

public ValueAndType(Class type) {
this.type = type;
this.value = Optional.empty();
}
}

NbProjectInfoBuilder(Project project) {
this.project = project;
this.gradleVersion = VersionNumber.parse(project.getGradle().getGradleVersion());
// checked that version 7.6.0 > 7.6.0-rc-1 in the VersionNumber order
this.adapter = sinceGradleOrDefault("7.6.0-rc-1", () -> new GradleInternalAdapter.Gradle76(project), () -> new GradleInternalAdapter(project));
}

private NbProjectInfoModel model = new NbProjectInfoModel();

public NbProjectInfo buildAll() {
NbProjectInfoModel model = new NbProjectInfoModel();
adapter.setModel(model);
runAndRegisterPerf(model, "meta", this::detectProjectMetadata);
detectProps(model);
detectLicense(model);
Expand All @@ -217,7 +232,7 @@ public NbProjectInfo buildAll() {
storeGlobalTypes(model);
return model;
}

@SuppressWarnings("null")
private void detectDistributions(NbProjectInfoModel model) {
if (project.getPlugins().hasPlugin("distribution")) {
Expand Down Expand Up @@ -365,29 +380,15 @@ private void detectConfigurationArtifacts(NbProjectInfoModel model) {
* @param model
*/
private void detectAdditionalPlugins(NbProjectInfoModel model) {
final PluginManagerInternal pmi;
PluginRegistry reg;
if (project.getPluginManager() instanceof PluginManagerInternal) {
pmi = (PluginManagerInternal)project.getPluginManager();
} else {
if (!adapter.hasPluginManager()) {
return;
}
if (project instanceof ProjectInternal) {
reg = ((ProjectInternal)project).getServices().get(PluginRegistry.class);
} else {
reg = null;
}
LOG.lifecycle("Detecting additional plugins");
final Set<String> plugins = new LinkedHashSet<>();

project.getPlugins().matching((Plugin p) -> {
for (Class c = p.getClass(); c != null && c != Object.class; c = c.getSuperclass()) {
Class fc = c;
// with Gradle 7.1+, plugins can be better enumerated. Prior to 7.1 I can only get IDs for registry-supplied plugins.
Optional<PluginId> id = sinceGradleOrDefault("7.1", () -> pmi.findPluginIdForClass(fc), Optional::empty); // NOI18N
if (!id.isPresent() && reg != null) {
id = reg.findPluginForClass(c);
}
Optional<PluginId> id = adapter.findPluginId(c);
if (id.isPresent()) {
LOG.info("Plugin: {} -> {}", id.get(), p);
plugins.add(id.get().getId());
Expand Down Expand Up @@ -537,14 +538,7 @@ private void inspectObjectAndValues(Class clazz, Object object, String prefix, M
}

private boolean isMutableType(Object potentialValue) {
if (potentialValue instanceof PropertyInternal) {
return true;
} else if ((potentialValue instanceof NamedDomainObjectContainer) && (potentialValue instanceof HasPublicType)) {
return true;
} else if (potentialValue instanceof Iterable || potentialValue instanceof Map) {
return true;
}
return false;
return adapter.isMutableType(potentialValue);
}

private void inspectObjectAndValues0(Class clazz, Object object, String prefix, Map<String, Map<String, String>> globalTypes, Map<String, String> propertyTypes, Map<String, Object> defaultValues, Set<String> excludes, boolean type) {
Expand Down Expand Up @@ -640,24 +634,15 @@ private void inspectObjectAndValues0(Class clazz, Object object, String prefix,
// Provider must NOT be asked for a value, otherwise it might run a Task in order to compute
// the value.
try {
value = mclazz.getProperty(object, propName);
if (Provider.class.isAssignableFrom(t)) {
Object potentialValue = mclazz.getProperty(object, propName);
if (potentialValue instanceof ProviderInternal) {
ProviderInternal provided = (ProviderInternal) potentialValue;
t = provided.getType();
ExecutionTimeValue etv;
etv = provided.calculateExecutionTimeValue();
if (etv.isFixedValue()) {
value = etv.getFixedValue();
}
} else {
value = potentialValue;
if (value != null) {
t = value.getClass();
ValueAndType vt = adapter.findPropertyValueInternal(propName, value);
if (vt != null) {
t = vt.type;
if (vt.value.isPresent()) {
value = vt.value.get();
}
}
} else {
value = mclazz.getProperty(object, propName);
}
} catch (RuntimeException ex) {
// just ignore - the property value cannot be obtained
Expand Down Expand Up @@ -1611,7 +1596,7 @@ private Set<Configuration> configurationsToSave() {
.collect(Collectors.toSet());
}

private interface ExceptionCallable<T, E extends Throwable> {
interface ExceptionCallable<T, E extends Throwable> {
public T call() throws E;
}

Expand Down

0 comments on commit 31c6618

Please sign in to comment.