Skip to content

Update project if needed #547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed

Conversation

bdshadow
Copy link
Contributor

although https://github.com/eclipse/eclipse.jdt.ls/pull/545/files was already merged, while i was working on this solution, still want to offer checking if pom.xml was changed not by comparing time, but by comparing md5 digests. In my opinion, it's more reliable, than checking the modification time of the whole workspace.

This PR is far not ideal, and will be updated, in case the idea is accepted.

@fbricon
Copy link
Contributor

fbricon commented Feb 12, 2018

Although I agree using a hash-based comparison is more robust than the current timestamp check, the current PR has 2 major flaws:

  • hashes are not serialized, so subsequent server(JVM) starts will ignore previous hash check, so we lose the benefits of the current timestamp-based solution
  • more importantly, explicit/manual update command is ignored if the pom is deemed unchanged, which is wrong, if you changed a parent pom for instance and want to update one of its modules.

@fbricon
Copy link
Contributor

fbricon commented Feb 12, 2018

Well this PR proves something's wrong with #545 tests, since they should have failed with those changes

@bdshadow bdshadow force-pushed the update_project_if_needed branch from 26db36f to e190b15 Compare February 12, 2018 15:27
@bdshadow
Copy link
Contributor Author

@fbricon could you, please, take a look at the changes. I tried to fix your 2nd remark

@bdshadow
Copy link
Contributor Author

@fbricon the build is fine for me locally. No test fails. Could you, please, restart the build in Jenkins and look at this pr again?

@fbricon
Copy link
Contributor

fbricon commented Feb 15, 2018

test this please

@@ -26,23 +35,47 @@
*/
public class MavenBuildSupport implements IBuildSupport {

private static Map<Path, String> pomDigests = new ConcurrentHashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We definitely need to serialize that map to disk, either after it changes (safer) or on workspace shutdown. Else subsequent server(JVM) starts will ignore previous hash check so it'll be a regression in behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fbricon ok, working on it. Is there a special place where eclipse.jdt.ls serializes objects to? Or maybe you can advise the path

@bdshadow bdshadow force-pushed the update_project_if_needed branch from e190b15 to f2a8168 Compare February 19, 2018 11:10
* - a project to update
* @param force
* - defines if the <code>project</code> must be updated despite of
* no changes of pom.xml are made
Copy link
Contributor

@fbricon fbricon Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no changes in the build descriptor are made

@@ -220,7 +220,8 @@ public void fileChanged(String uriString, CHANGE_TYPE changeType) {
FeatureStatus status = preferenceManager.getPreferences().getUpdateBuildConfigurationStatus();
switch (status) {
case automatic:
updateProject(resource.getProject());
// do not force the build, because it's not started by user and should be done only if pom.xml has changed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/pom.xml/build file/

@@ -315,7 +316,7 @@ public IProject createJavaProject(IProject project, IProgressMonitor monitor) th
return project;
}

public Job updateProject(IProject project) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we might as well keep the update(IProject project) flavour, to avoid the need to change to the more verbose update(project, false) version all over the place. Needs javadoc explaining the 2 signatures

Copy link
Contributor

@tsmaeder tsmaeder Mar 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method is only called twice from production code, so I think keeping it as is makes sense.

configurationManager.updateProjectConfiguration(request, monitor);
// maven update should not be forced here, because it's a new project, just imported into workspace and
// will be run automatically, because pom.xml-s have just appeared
new MavenBuildSupport(configurationManager).update(project, monitor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move MavenBuildSupport instantiation out of the loop

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do projectsManage.getBuildSupport(project).update(project, monitor)? The assumption we have is that MavenBuildSupport is stateless, but I don't really like that.

String newDigestStr = Arrays.toString(digest);
synchronized (pomDigests) {
String prevDigest = pomDigests.put(path.toString(), newDigestStr);
if (prevDigest == null || !prevDigest.equals(newDigestStr)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if (!newDigestStr.equals(prevDigest)) 

static {
File workspaceFile = ResourcesPlugin.getWorkspace().getRoot().getRawLocation().makeAbsolute().toFile();
SERIALIZATION_FILE = new File(workspaceFile, POM_SERLIZATION_FILE_NAME);
if (SERIALIZATION_FILE.exists()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFile()?

synchronized (pomDigests) {
String prevDigest = pomDigests.put(path.toString(), newDigestStr);
if (prevDigest == null || !prevDigest.equals(newDigestStr)) {
serializePomDigests();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we serialize on every change?

configurationManager.updateProjectConfiguration(request, monitor);
// maven update should not be forced here, because it's a new project, just imported into workspace and
// will be run automatically, because pom.xml-s have just appeared
new MavenBuildSupport(configurationManager).update(project, monitor);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not do projectsManage.getBuildSupport(project).update(project, monitor)? The assumption we have is that MavenBuildSupport is stateless, but I don't really like that.

@@ -26,23 +41,87 @@
*/
public class MavenBuildSupport implements IBuildSupport {

private static final String POM_SERLIZATION_FILE_NAME = ".pom-digests";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like the fact that we're using a static map here. I guess it comes from the fact that MavenBuildSupport is used as a stateless object. I'd rather change that and keep the map in the instance.
Also, we should use the workspace state location available to eclipse plugins instead of using the workspace root.

import org.osgi.util.tracker.ServiceTracker;

public class JavaLanguageServerPlugin implements BundleActivator {
public class JavaLanguageServerPlugin extends Plugin {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getStateLocation() is on plugin

@@ -102,6 +104,7 @@ protected IStatus run(IProgressMonitor monitor) {

public ProjectsManager(PreferenceManager preferenceManager) {
this.preferenceManager = preferenceManager;
this.mavenBuildSupport = new MavenBuildSupport(MavenPlugin.getProjectConfigurationManager());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1. I'd like to decouple ProjectsManager from underlying build supports eventually. I expect buildSupports() implementation to load IBuildSupport instances from extension points, and don't want to make any assumption about the statefulness of each IBuildSupport.

Copy link
Contributor

@tsmaeder tsmaeder Mar 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're creating a new instance every time you need one, you ARE making an assumption, namely that build support is stateless. Keeping the same instance allows the implementers to be stateless or not.
IMNSHO (;-)) this is much better than keeping the digests in a static map.

MavenUpdateRequest request = new MavenUpdateRequest(project, MavenPlugin.getMavenConfiguration().isOffline(), true);
configurationManager.updateProjectConfiguration(request, monitor);
}

private boolean needsMavenUpdate(IProject project) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the more I think of it the less I like this implementation. This method should simply check if a project needs an update. Updating the digests map should be done once the update is effectively triggered

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the two are really equivalent here, no? If the the digest is changed and an update needed, the project WILL be updated. So while not pretty, I think the logic is correct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it works for now. Until needsMavenUpdate is called from somewhere else (even in the same class since it's private) and no update is performed, then things will go wrong.
calling needsMavenUpdate twice in a row should yield the same results, always. Else, please change the method name to indicate it does more than checking.

@@ -25,24 +39,84 @@
*
*/
public class MavenBuildSupport implements IBuildSupport {
private Map<String, String> pomDigests;

private static final String POM_SERLIZATION_FILE_NAME = ".pom-digests";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POM_SERIALIZATION_FILE_NAME

try (ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(f))) {
outStream.writeObject(pomDigests);
} catch (IOException e) {
JavaLanguageServerPlugin.logException("Exception occured while serizalizion of pom digests", e);
Copy link
Contributor

@fbricon fbricon Mar 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while serializing pom digests

try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) {
return (Map<String, String>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
JavaLanguageServerPlugin.logException("Exception occured while deserizalizion of pom digests", e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

while deserializing pom digests

@fbricon
Copy link
Contributor

fbricon commented Mar 27, 2018

Ok so one major problem with this approach is we only serialize poms when a file change is detected. We should fingerprint them before importing them too, else you'll import the project, save a pom.xml without changing it, it'll be detected as an actual change and update will run

@tsmaeder tsmaeder force-pushed the update_project_if_needed branch from 26d4698 to acfbc4b Compare April 6, 2018 10:48
@tsmaeder tsmaeder force-pushed the update_project_if_needed branch from acfbc4b to 8444b87 Compare April 11, 2018 11:34
*
*/
public class DigestStore {
private Map<String, String> pomDigests;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fileDigests

eventually we could use the same mechanism for Gradle. Since this class has no coupling to Maven, I'd prefer we treat it as file-type agnostic.


private static final String POM_SERIALIZATION_FILE_NAME = ".pom-digests";

public DigestStore(Plugin plugin) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need a plugin object. Storage location path should be enough

@@ -121,6 +123,7 @@ public void start(BundleContext bundleContext) throws BackingStoreException {
JavaManipulation.setPreferenceNodeId(PLUGIN_ID);
preferenceManager = new PreferenceManager();
initializeJDTOptions();
digestStore = new DigestStore(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new DigestStore(getStateLocation())

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duh!

Signed-off-by: Thomas Mäder <[email protected]>

}

private void serializePomDigests() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

serializeFileDigests

}

@SuppressWarnings("unchecked")
private Map<String, String> deserializePomDigests() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deserializeFileDigests

Signed-off-by: Thomas Mäder <[email protected]>
@fbricon
Copy link
Contributor

fbricon commented Apr 13, 2018

squash merged as 6c64df5

@fbricon fbricon closed this Apr 13, 2018
@fbricon fbricon added this to the Mid April 2018 milestone Apr 13, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants