Skip to content

Commit

Permalink
Project (re)load implementation for Gradle with trust grant.
Browse files Browse the repository at this point in the history
  • Loading branch information
sdedic committed Aug 12, 2024
1 parent ebf7d27 commit 9cf5c1c
Show file tree
Hide file tree
Showing 16 changed files with 991 additions and 4 deletions.
2 changes: 1 addition & 1 deletion extide/gradle/manifest.mf
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
AutoUpdate-Show-In-Client: true
OpenIDE-Module: org.netbeans.modules.gradle/2
OpenIDE-Module-Implementation-Version: 1
OpenIDE-Module-Layer: org/netbeans/modules/gradle/layer.xml
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/gradle/Bundle.properties
OpenIDE-Module-Java-Dependencies: Java > 17
OpenIDE-Module-Specification-Version: 2.43
1 change: 1 addition & 0 deletions extide/gradle/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ javadoc.apichanges=${basedir}/apichanges.xml
nbm.module.author=Laszlo Kishalmi
source.reference.netbeans-gradle-tooling.jar=netbeans-gradle-tooling/src/main/groovy

spec.version.base=2.43.0
test-unit-sys-prop.test.netbeans.dest.dir=${netbeans.dest.dir}
test-unit-sys-prop.java.awt.headless=true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public static ProjectState getProjectState(Project p, boolean attemptLoad) throw
return ProjectReloadInternal.getInstance().getProjectState0(p, Lookup.EMPTY, false).second();
}
try {
return withProjectState(p, StateRequest.load().toQuality(Quality.NONE).tryQuality(Quality.SIMPLE).consistent(false)).get();
return withProjectState(p, StateRequest.load().toQuality(Quality.NONE).tryQuality(Quality.SIMPLE).consistent(false).offline()).get();
} catch (ExecutionException ex) {
if (ex.getCause() instanceof ProjectOperationException) {
throw (ProjectOperationException)ex.getCause();
Expand Down Expand Up @@ -611,7 +611,7 @@ public StateRequest toQuality(Quality q) {
*/
public StateRequest tryQuality(Quality q) {
this.tryQuality = q;
if (this.minQuality.isWorseThan(q)) {
if (q.isWorseThan(minQuality)) {
this.minQuality = q;
}
return this;
Expand Down
2 changes: 2 additions & 0 deletions java/gradle.dependencies/nbproject/project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ spec.version.base=1.3.0

test-unit-sys-prop.test.netbeans.dest.dir=${netbeans.dest.dir}
test-unit-sys-prop.java.awt.headless=true
#test-unit-sys-prop.netbeans.debug.gradle.info.action=true
#test-unit-sys-prop.org.netbeans.modules.gradle.dependencies
2 changes: 1 addition & 1 deletion java/gradle.dependencies/nbproject/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<compile-dependency/>
<run-dependency>
<release-version>2</release-version>
<specification-version>2.38</specification-version>
<implementation-version/>
</run-dependency>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
/*
* 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.reload;

import java.beans.PropertyChangeListener;
import java.io.File;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.gradle.GradleProject;
import org.netbeans.modules.gradle.NbGradleProjectImpl;
import org.netbeans.modules.gradle.ProjectTrust;
import org.netbeans.modules.gradle.api.GradleBaseProject;
import org.netbeans.modules.gradle.api.NbGradleProject;
import org.netbeans.modules.gradle.spi.GradleFiles;
import org.netbeans.modules.project.dependency.ProjectOperationException;
import org.netbeans.modules.project.dependency.ProjectReload.Quality;
import static org.netbeans.modules.project.dependency.ProjectReload.Quality.CONSISTENT;
import org.netbeans.modules.project.dependency.ProjectReload.StateRequest;
import org.netbeans.modules.project.dependency.spi.ProjectReloadImplementation;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;

/**
* Implementation of Project State + Reload API for Gradle projects.
* @author sdedic
*/
@ProjectServiceProvider(service = ProjectReloadImplementation.class, projectType = NbGradleProject.GRADLE_PROJECT_TYPE)
public class GradleReloadImplementation implements ProjectReloadImplementation {
private static final Logger LOG = Logger.getLogger(GradleReloadImplementation.class.getName());

private final Project project;
private final PropertyChangeListener reloadL;

private Reference<ProjectStateData> last = new WeakReference<>(null);


public GradleReloadImplementation(Project project) {
this.project = project;
this.reloadL = (pch) -> {
if (NbGradleProject.PROP_PROJECT_INFO.equals(pch.getPropertyName())) {
LOG.log(Level.FINE, "Project {0} reloaded", project);
}
};
NbGradleProject gp = NbGradleProject.get(project);
gp.addPropertyChangeListener(WeakListeners.propertyChange(reloadL, gp));
}

private static void includeFile(File f, Collection<FileObject> into) {
if (f == null) {
return;
}
FileObject fo = FileUtil.toFileObject(f);
if (fo == null) {
return;
}
into.add(fo);
}

private Set<FileObject> findProjectFiles(boolean forReload) {
NbGradleProject nbgp = NbGradleProject.get(project);
GradleFiles gf = nbgp.getGradleFiles();
Set<FileObject> files = new HashSet<>();

if (forReload) {
gf.getProjectFiles().forEach(f -> includeFile(f, files));
includeFile(gf.getFile(GradleFiles.Kind.ROOT_PROPERTIES), files);
includeFile(gf.getFile(GradleFiles.Kind.USER_PROPERTIES), files);
includeFile(gf.getFile(GradleFiles.Kind.ROOT_SCRIPT), files);
} else {
File f = gf.getSettingsScript();
if (gf.isRootProject() || f.getParentFile().equals(gf.getProjectDir())) {
includeFile(gf.getSettingsScript(), files);
}
includeFile(gf.getBuildScript(), files);
}
includeFile(gf.getFile(GradleFiles.Kind.PROJECT_PROPERTIES), files);
return files;
}

public ProjectStateData getProjectData() {
NbGradleProject nbgp = NbGradleProject.get(project);
long time = nbgp.getEvaluateTime();

synchronized (this) {
ProjectStateData d = last.get();
if (d != null && d.getTimestamp() == time) {
return d;
}
}

// PENDING: should also watch out for files that do not exist, but Gradle still looks for them.
// For example, buildscript may be initially missing, but can be created which should make to ProjectState inconsistent.
Collection<FileObject> files = findProjectFiles(true);
NbGradleProject.Quality pq = nbgp.getQuality();
Quality q;

switch (pq) {
case FALLBACK:
q = Quality.FALLBACK;
if (ProjectTrust.getDefault().isTrusted(project)) {
q = Quality.FALLBACK;
} else {
q = Quality.UNTRUSTED;
}
break;
case EVALUATED:
q = Quality.BROKEN;
break;
case SIMPLE:
q = Quality.SIMPLE;
break;
case FULL:
q = Quality.LOADED;
break;
case FULL_ONLINE:
q = Quality.RESOLVED;
break;
default:
q = Quality.BROKEN;
break;
}
ProjectStateData sd = ProjectStateData.builder(q).
files(files).
attachLookup(NbGradleProject.get(project).curretLookup()).
build();
/*
ProjectStateControl track = new ProjectStateControl(
"gradle", null, files, pq == NbGradleProject.Quality.FALLBACK || pq.betterThan(NbGradleProject.Quality.EVALUATED),
q, time, NbGradleProject.get(project).projectDataLookup()
);
*/
synchronized (this) {
// states.add(new WeakReference<>(track));
last = new WeakReference<>(sd);
}
return sd;
}

/**
* Exception classes known to be thrown if an artifact cannot be downloaded. For Plugins, the resolution against
* local repository fails, so we have to guess from the quality + "unknown plugin" information.
*/
private static final String[] OFFLINE_EXCEPTION_CLASSES = {
"org.gradle.api.plugins.UnknownPluginException", // NOI18N
"org.netbeans.modules.gradle.tooling.NeedOnlineModeException" // NOI18N
};

/**
* Process the known exception classes into match pattern at startup.
*/
private static final Pattern OFFLINE_EXCEPTIONS_PATTERN = Pattern.compile(String.join("|", OFFLINE_EXCEPTION_CLASSES));

@NbBundle.Messages({
"# {0} - number of files",
"ERROR_Project_Out_Of_Sync=Project files ({0}) are not saved",
"# {0} - project name",
"TEXT_RefreshProject=Reloading project {0}",
"# {0} - project name",
"# {1} - error message",
"ERRROR_ProjectNotLoadable=Project {0} cannot be loaded: {1}",
"# {0} - project name",
"ERROR_NeedOnlineOperation=Could not resolve project {0} in offline mode."
})
@Override
public CompletableFuture reload(Project project, StateRequest stateRequest, LoadContext context) {
if (!(project instanceof NbGradleProjectImpl)) {
return null;
}
NbGradleProjectImpl nbgp = (NbGradleProjectImpl)project;
NbGradleProject.Quality aimQuality;
CompletionStage<GradleProject> loadFuture;
boolean offline = false;
switch (stateRequest.getMinQuality()) {
case NONE:
// at least try :)
if (stateRequest.getTargetQuality().isAtLeast(Quality.SIMPLE)) {
aimQuality = NbGradleProject.Quality.FALLBACK;
offline = true;
break;
} else {
return CompletableFuture.completedFuture(getProjectData());
}
case FALLBACK:
aimQuality = NbGradleProject.Quality.FALLBACK;
break;
case BROKEN:
aimQuality = NbGradleProject.Quality.EVALUATED;
break;
case SIMPLE:
aimQuality = NbGradleProject.Quality.SIMPLE;
break;
case LOADED:
aimQuality = NbGradleProject.Quality.FULL;
break;
case CONSISTENT:
case RESOLVED:
aimQuality = stateRequest.isOfflineOperation() ? NbGradleProject.Quality.FULL : NbGradleProject.Quality.FULL_ONLINE;
break;
default:
throw new AssertionError(stateRequest.getMinQuality().name());

}
if (stateRequest.isGrantTrust()) {
ProjectTrust.getDefault().isTrustedPermanently(project);
}
LOG.log(Level.FINE, "Request to reload {0}: aimedQuality={1}, force={2}", new Object[] { project, aimQuality, stateRequest.isForceReload() });
CompletableFuture<ProjectStateData> content = new CompletableFuture<>();

loadFuture = nbgp.projectWithQualityTask(NbGradleProject.loadOptions(aimQuality).
setDescription(stateRequest.getReason()).
setForce(stateRequest.isForceReload()).
setCheckFiles(stateRequest.isConsistent()).
setOffline(stateRequest.isOfflineOperation() || offline)
);
loadFuture.thenAccept((p) -> {
NbGradleProject gp = NbGradleProject.get(project);
LOG.log(Level.FINE, "Project {0} reload complete, quality: {1}, time: {2}", new Object[] { project, gp.getQuality(), gp.getEvaluateTime() });
boolean failed = gp.getQuality().worseThan(aimQuality);
if (stateRequest.getMinQuality() == Quality.RESOLVED && gp.getQuality().atLeast(NbGradleProject.Quality.FULL)) {
failed = false;
}
if (failed) {
GradleBaseProject gbp = GradleBaseProject.get(project);
boolean offlineError = gbp.getProblems().stream().anyMatch(prb -> OFFLINE_EXCEPTIONS_PATTERN.matcher(prb.getErrorClass()).find());

// handle online error specially:
if (offlineError) {
PartialLoadException partialLoad = new PartialLoadException(getProjectData(), Bundle.ERROR_NeedOnlineOperation(ProjectUtils.getInformation(project).getDisplayName()),
new ProjectOperationException(project, ProjectOperationException.State.OFFLINE, Bundle.ERROR_NeedOnlineOperation(ProjectUtils.getInformation(project).getDisplayName())
));
content.completeExceptionally(partialLoad);
return;
}
}
content.complete(getProjectData());
});

return content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example;

import io.micronaut.runtime.Micronaut;

public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
public class Something {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<configuration>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<withJansi>true</withJansi>
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n</pattern>
</encoder>
</appender>

<root level="info">
<appender-ref ref="STDOUT" />
</root>
</configuration>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example;

import io.micronaut.runtime.EmbeddedApplication;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;

import jakarta.inject.Inject;

@MicronautTest
class DemoTest {

@Inject
EmbeddedApplication<?> application;

@Test
void testItWorks() {
Assertions.assertTrue(application.isRunning());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
micronautVersion=3.10.2
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading

0 comments on commit 9cf5c1c

Please sign in to comment.