2

I need to set up an Eclipse project with an additional builder that enhances the Java bytecode produced by an earlier builder (ideally Eclipse's own). I managed to get this builder to run and enhance the Eclipse Java builder output properly but seconds later Eclipse re-runs its Java builder and resets the bytecode back. It does not rerun my enhancement builder.

My setup

  • Imported as a "Gradle project" into Eclipse 2019-12 (with Buildship).
  • Added manually (and automated with Gradle) a custom Ant builder (that ends up calling Gradle) to enhance the code that Eclipse Java builder produces in bin/main, in place. This builder is set to run on Manual Build and Auto Build and not After a "Clean" or During a "Clean".
  • By default the above ends up having three builders, top-to-bottom: 1. Gradle Project Builder, 2. Java Builder and 3. my bytecode enhancement builder (yes, it is listed last).

Alternatives I tried

  1. Some combinations of setting my builder to run after/during a "Clean" as well without success. Not sure what exact events these relate to, really.
  2. Had the builder refresh the project after ... and also not - did not help.
  3. Try to remove the Java Builder using the following bit in Gradle script (didn't work - it comes back on its own):

    eclipse {
        project {
            file {
                whenMerged { projectFile ->
                    projectFile.buildCommands.removeAll { it.name == 'org.eclipse.jdt.core.javabuilder' }
                }
            }
        }
     }
    
  4. Tried disabling the Java builder manually and have my bytecode enhancement builder also build the files itself (using Gradle). This stores the following file org.eclipse.jdt.core.javabuilder.launch file with the following content ... but upon restart the builder is re-enabled:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <launchConfiguration type="org.eclipse.ant.AntBuilderLaunchConfigurationType">
    <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_BUILDER_ENABLED" value="false"/>
    <stringAttribute key="org.eclipse.ui.externaltools.ATTR_DISABLED_BUILDER" value="org.eclipse.jdt.core.javabuilder"/>
    <mapAttribute key="org.eclipse.ui.externaltools.ATTR_TOOL_ARGUMENTS"/>
    <booleanAttribute key="org.eclipse.ui.externaltools.ATTR_TRIGGERS_CONFIGURED" value="true"/>
    </launchConfiguration>
    
  5. I tried (and failed) to find if there is some workspace file (as opposed to project file) being changed (as well) to disable the Java builder.

Questions

  1. What is the "proper" way to set up Eclipse for post-compilation bytecode enhancement?
  2. What causes Eclipse to re-run previous builders without re-running mine?
  3. Is there a way to fix (1)?
  4. How to reliably disable the Java builder?

Can anyone help? Thanks!

UPDATE Additional details I added 12 builders and made them all append output to the same log file to research. The 12 extra builders are just informational - 4 before the Java Builder, 4 between the Java and the enhancement builder and 4 after the enhancement builder. Each of the 12 run in only one of the four conditions (hence 3x4). They are arranged as follows:

  1. Gradle Project Builder
  2. 1a-after-clean (runs only After a "Clean")
  3. 1b-manual (runs only During manual builds)
  4. 1c-auto (runs only During auto builds)
  5. 1d-during-clean (runs only During a "Clean")
  6. Java Builder
  7. 2a-after-clean (runs only After a "Clean")
  8. 2b-manual (runs only During manual builds)
  9. 2c-auto (runs only During auto builds)
  10. 2d-during-clean (runs only During a "Clean")
  11. Bytecode Enhancement Builder
  12. 3a-after-clean (runs only After a "Clean")
  13. 3b-manual (runs only During manual builds)
  14. 3c-auto (runs only During auto builds)
  15. 3d-during-clean (runs only During a "Clean")

Each of the 12 informational builders writes time, its name and the size of a chosen test class. Unenhanced it is 46243 bytes long. When enhanced it becomes 53338 bytes long.

Here's the log after running "Clean" on this project alone ("Build automatically" is enabled):

20:19:19
1d-during-clean
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:10 Test.class

20:19:19
2d-during-clean
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:10 Test.class

20:19:20
1c-auto
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:10 Test.class

20:19:27
2c-auto
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class


Buildfile: /.../some-ant.xml

run-gradle:
        [echo] Running Gradle: --parallel :...:enhanceEclipseBytecode
        ...
        [java] > Task :...:enhanceBytecode
        [java] Enhanced class: ...Test in ...
        ...
        [java] Enhanced 205 classes.
        [java] > Task :...:enhanceEclipseBytecode
        [java] BUILD SUCCESSFUL in 15s
        [java] 2 actionable tasks: 2 executed
BUILD SUCCESSFUL
Total time: 15 seconds
20:19:44
1c-auto
-rw-r--r--  1 Learner  ...\...  53338  3 Mar 20:19 Test.class

20:19:46
1c-auto
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:46
2c-auto
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:46
3b-manual
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:46
3c-auto
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:46
3d-during-clean
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:57
1c-auto
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:57
2c-auto
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:57
3b-manual
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:57
3c-auto
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

20:19:57
3d-during-clean
-rw-r--r--  1 Learner  ...\...  46243  3 Mar 20:19 Test.class

UPDATE 2: Minimum example to reproduce

  1. Create a folder - name it what you wish.
  2. In that folder create build.grade file with the following content:

    buildscript {
        repositories {
            mavenCentral()
        }
    
        dependencies {
            classpath 'org.hibernate:hibernate-gradle-plugin:5.4.2.Final'
        }
    }
    
    plugins {
        id 'java'
        id 'eclipse'
    }
    
    apply plugin: 'org.hibernate.orm'
    
    repositories {
        mavenCentral()
    }
    
    dependencies {
        implementation 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final'
    }
    
    hibernate {
        sourceSets = [ project.sourceSets.main ]
        enhance {
            enableLazyInitialization = true;
            enableDirtyTracking = true;
            enableAssociationManagement = false;
            enableExtendedEnhancement = false;
        }
    }
    
  3. Create a src/main/java/learner/TestEntity.java in there too as follows:

    package learner;
    
    import javax.persistence.*;
    
    @Entity
    public class TestEntity {
        @Id
        @Column(name = "id", nullable = false, updatable = false)
        private Long id = null;
    
        @Column(name = "name", columnDefinition = "TEXT")
        private String name = null;
    
        public Long getId() {
            return id;
        }
    
        public void setId(final Long id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(final String name) {
            this.name = name;
        }
    }
    
  4. Execute gradle compileJava. Open the resulting build/classes/java/main/learner/TestEntity.class binary in an ASCII or hex viewer and observe stuff like $$_hibernate_write_name in there.

  5. Import this project into Eclipse (say 2019-12) as a Gradle project and build it. Open the resulting bin/main/learner/TestEntity.class and observe none of that.
Learner
  • 1,215
  • 1
  • 11
  • 26
  • My related question from Gradle perspective on Gradle forum: https://discuss.gradle.org/t/how-to-make-projects-needing-bytecode-enhancement-work-in-eclipse-buildship/34985 – Learner Mar 03 '20 at 21:13
  • 1. Add a [project builder after the Java builder](https://help.eclipse.org/2019-12/topic/org.eclipse.platform.doc.user/gettingStarted/qs-92_project_builders.htm). 2. Maybe changed files that are not derived (not in the output folder) or the Java project nature. 3.+4. The Java builder incremental compiles the code and finds problem. For Java project it does not make sense to disable it. Does the bytecode enhancer work incremental? – howlger Mar 03 '20 at 22:57
  • 1. Custom builder is already after the Java builder. 2. The project does have the Java nature. Nothing else changes other than the output folder. I don't know if Eclipse needs a refresh to "notice" and take account of the output folder changes or not. 3+4. I do not wish to disable it - that was an alternative to being unable to work with it. Enhancer is not incremental but is idempotent - rerunning it on already processed output does nothing and it takes 5-7 seconds regardless (quick). – Learner Mar 03 '20 at 23:53
  • Added lots of details. – Learner Mar 04 '20 at 01:42
  • _"a custom Ant builder (that ends up calling Gradle)"_ ← Does the Gradle script contain `eclipse { ... }`. If yes, this might trigger another build. What happens if the Ant project builder just removes a single `.class` file and does not touch other files or run Gradle or something else? Have a look at the Eclipse `org.eclipse.help.webap` plug-in [where an Ant project builder is used to compile the `jsp` files after the Java builder](https://git.eclipse.org/c/platform/eclipse.platform.ua.git/tree/org.eclipse.help.webapp/.project#n23). – howlger Mar 04 '20 at 12:54
  • Gradle does contain eclipse {...}, that is how integration with Buildship works. Gradle also does bytecode enhancement step on its own as well. However, Gradle itself isn't invoked the second time - I can confirm that. Re JSP precompilation - that is different and simpler, not affecting the same set of files that Eclipse outputs. – Learner Mar 04 '20 at 13:08
  • Buildship does not require having an `eclipse {...}`. Touching, creating or deleting non-derived files (_derived_ is an Eclipse folder/file attribute) will trigger a build. Please answer my question _"What happens if the Ant project builder just removes a single `.class` file and does not touch other files or run Gradle or something else?"_ Please provide a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – howlger Mar 04 '20 at 14:20
  • Buildship does not require it but it doesn't put bytecode enhancement tasks otherwise in Gradle in effect in Eclipse (specifically the 'org.hibernate.orm' plugin), probably to promote the use of Eclipse's own compiler. So this is already a workaround for that. Regardless, there is something fishy about Eclipse's builder and, perhaps, how Buildship interacts with Eclipse. I provided plenty of details already but, sure, will try to provide a minimum example. – Learner Mar 04 '20 at 15:54
  • Sure, you provided plenty of details, but nothing to reproduce the issue. Removing the Java Builder was the wrong way to go and it is unclear to me whether this has been completely reversed or still causing issues or whether other things are still based on it. Before you can add the Ant project builder via Gradle, it must work without calling Gradle or triggering a subsequent build overriding the enhanced files. – howlger Mar 04 '20 at 16:20
  • Just added the minimum example. – Learner Mar 04 '20 at 16:27
  • Re "Removing the Java Builder was the wrong way to go". Of course it is wrong! It isn't something we wish. It is something attempted as things don't work. As you can see in the minimum example, there is no removal of Java builder but things don't happen. We tried adding a step to work with Java builder's output but that gets overwritten. One simple way to set this workaround up is to add a builder (Ant or Program, doesn't matter) to do `gradle compileJava` and `build/classes/java/main/learner/TestEntity.class` over `bin/main/learner/TestEntity.class` – Learner Mar 04 '20 at 16:40
  • To reproduce what? I mean, why do all `.class` files need to be enhanced each time a `.java` file is saved? Why is it not sufficient to run the Gradle enhance task before running or debugging the Java application (e.g. manually one after the other or via a launch group as one action)? – howlger Mar 04 '20 at 18:08
  • Because developers also need to (1) run the application from within Eclipse, not Gradle and (2) they also run test code (not the entire application) that requires the main code to be enhanced. This is not about the reason why. There are many examples why code enhancement is required and/or a good thing. The problem here is how to set up Eclipse in this environment for it. – Learner Mar 04 '20 at 18:48
  • I have a complete minimum project with informational/simulation ant builders... in a 9.4K zip file. Not sure where is it acceptable to post it. ... posted at https://discuss.gradle.org/t/how-to-make-projects-needing-bytecode-enhancement-work-in-eclipse-buildship/34985/8?u=learner – Learner Mar 04 '20 at 18:57
  • I figured it out MOSTLY. Will provide my answer when I can. – Learner Mar 04 '20 at 20:36
  • In your case, running the enhancer as project builder is not required. Since it is not necessary to compile other classes or to get warnings, enhancing as project builder adds no value. Project builder means to run it on save, so it has to be fast (in milliseconds, not seconds). To enhance the `.class` file just before run/debug your application or test suite from inside of Eclipse, use a so-called [_Launch Group_](https://stackoverflow.com/a/47302032/6505250). Launch configurations can be shared, so they only need to be configured once. It's really that simple. – howlger Mar 05 '20 at 08:02
  • You are still missing the point. If Eclipse had implicit launch groups that automatically add preparatory steps before launching anything that comes out of a certain project, I'd be able to use that, yes. But it does not. Without build-time enhancement (however long it takes or not), every time one wants to launch something like a JUnit test or anything else from the projects, they would either have to manually launch the enhancement of manually create/alter the launch configs of what they want to run. It is really that messy. – Learner Mar 05 '20 at 12:44
  • Wow. Building the project itself takes longer and this isn't that significant. Sure, I don't *need* to use some features but I don't *need* to use Eclipse either. We could all do it with command-line, Gradle and `vi`. Some people here actually do. Note that this project is isolated and not changed often by most people. – Learner Mar 05 '20 at 15:45

1 Answers1

1

There were a few things I was unclear/wrong about and couldn't find relevant documentation to learn the details. Here's a summary of what needs to be known to get this right (some of which I got right from the get go, but not everything):

  1. Gradle/Buildship integration in Eclipse attempts to make use of Eclipse's internal compiler. This is by Eclipse's design and has its own advantages during development ... as well as disadvantages during times like this - inability to leverage the external/production builders (Gradle model in this case) to do the building. For this reason any bytecode enhancement (plugins or not) operating inside Gradle will have no effect in Eclipse at all (unless they do something Eclipse-specific). (I got this right)
  2. To perform bytecode enhancement in Eclipse (not apt or what some Eclipse plugin could do), one has to add custom builders and position them after the default Java builder (manually or automatically). (I got this right too)
  3. Out of box Eclipse offers two kinds of builders - "Ant" and "Program". No Gradle there. "Program" kind is not cross-platform, only Ant is. Ant can be used to do things or launch Gradle in a cross-platform way (Java exec on Gradle's main() method). (I got this right too)
  4. [I WAS WRONG HERE] Eclipse offers four "events" to which one can bind an Ant builder: After a "Clean", Manual Build, Auto Build and During a "Clean". I did not understand when do they run. For example, I thought that "During a Clean" runs while the clean is happening and that After a "Clean" is there to run after that, to allow the custom builder to do its own after-clean cleaning. I thought that this would be followed by Auto build if "Build automatically" is enabled or "Build immediately" is checked in the "Clean" dialog. This is NOT the case. After a "Clean" actually refers to a build step, not cleaning and will NOT be followed by the *Auto Build" step (that step only runs when one saves an edit and "Build Automatically" is enabled. In the builder's *.launch file this is much more apparent - the After a "Clean" is actually called full and Auto and Manual builds are called auto and incremental. This means that en enhancement builder has to be set to run on After a "Clean" in addition to Auto Build and Manual Build and should NOT be set to run on *During a "Clean". My mistake was to only set it to run for Auto and Manual builds.
  5. [I WAS WRONG HERE TOO] I specified the working set of relevant resources in the Build Options tab for the enhancement builder. I set those resources to be the source code (to be enhanced) and build.gradle (containing the enhancer). Since these do not change on most builds, Eclipse chooses not to run the builder. The now obvious 20-20 vision truth is that relevant resources for this builder are the Java builder's output binaries (and build.gradle), not the Java source code. However, this isn' the entirely correct choice (in isolation) either as Eclipse, in our case, ends up in an infinite loop - it thinks that the enhancer changed the binaries and, as it is set to run when binaries changed, runs the build again. We cannot NOT set the relevant resources at all as that seems to mean "everything/anything". The enhancer must be made in such a way to NOT even touch the files that are already enhanced [UPDATE] and that isn't enough. Read on.

I still don't definitively know why the informational builders I used to research this have their output appended to the common log file in order that isn't chronological. I can only assume that this has to do with Eclipse's output buffering and periodic writing to these files somehow.

[UPDATE 1]

  1. [I DIDN'T KNOW THIS] Eclipse has a workspace setting (checkbox, overridable per project) in Preferences -> Java -> Compiler -> Building -> Output Folder called "Rebuild class files modified by others" officially described as "Indicate whether class files which have been modified by others should be rebuilt to undo the modification.". By default it is unchecked/off, which seems right for this case. This, however, does not work as advertised. Whatever the setting is, Eclipse's Java Builder will react to its output changes (as if they were inputs, I call this a defect) and "undo the modification", causing infinite build loops. I found this symptom reported many times - search for this. With "correct" setup and no hacking the Java builder keeps undoing the modifications and Eclipse keeps re-running them, causing the infinite loop regardless of the setting.
  2. [HACK THAT SEEMS TO WORK PRESENTLY] In addition to having everything above set up correctly I modified the enhancer to ensure two things (either only one or both may be required, not sure): (a) that existing *.class files are NOT deleted and recreated but rewritten and (b) that their last modified time is changed back to what it was before the enhancement. This seems to trick the Eclipse's modification detection enough to break out of the loop even though the file sizes are different. This is with Eclipse 2019-12 (4.14.0.v20191210-0610) and it may stop working with any update. I hope they fix the infinite build loop defect by then.
Learner
  • 1,215
  • 1
  • 11
  • 26
  • Actually ... this is not the ultimate answer yet... In a larger project this, depending on how "relevant resources" are set up either ends up in an infinite builder loop (when "Build automatically" is enabled) or ends up having the initial problem. – Learner Mar 05 '20 at 04:43
  • Just added **[UPDATE 1]** to my answer above, to address what I stated in my previous comment above. – Learner Mar 06 '20 at 02:25
  • I posted a minimum working example (without the update 1 workarounds) at https://discuss.gradle.org/t/how-to-make-projects-needing-bytecode-enhancement-work-in-eclipse-buildship/34985/9 – Learner Mar 06 '20 at 02:42
  • Wrong and not an answer. For example, (4) what you call _four events_ are in fact build kinds (see the [documentation](https://help.eclipse.org/2019-12/topic/org.eclipse.platform.doc.user/gettingStarted/qs-93a_project_builder_targets.htm) I referred to in my first comment). Also, input (source) and output folders are a Java, not a project builder thing (the option mentioned in (6) is not about when but about what a build does). You have to know these basics, to not - as you did - create an infinite loop by writing into a log file, for example. – howlger Mar 06 '20 at 08:12
  • There are better solutions than a project builder that takes seconds even when changing a single source file. _Launch Groups_ or one of the [solutions Lance listed](https://discuss.gradle.org/t/how-to-make-projects-needing-bytecode-enhancement-work-in-eclipse-buildship/34985/3) can be used instead. – howlger Mar 06 '20 at 08:13
  • (a) It should not have to be done this way and, for that reason, I agree that it is wrong. However it is the answer as, in this situation only this works. (b) In (4) these are 4 *events* as *During a clean* is not a build - it is a clean and because these are triggers (trigger events) for subsequent builds. (c) The inputs and outputs cannot be both Java. For Eclipse's own Java builder the inputs are `*.java` but outputs are `*.class`. I didn't come here because I claimed to know everything - on the contrary. However nobody offered a solution - only to change the problem in a way I could not. – Learner Mar 06 '20 at 13:10
  • (d) The log file is not in any of the output folders. Even though it remains there today it does not cause loops. (e) Adding few seconds to keep the build up-to-date and working following the usual Eclipse experience is preferential to adding to the list of what developers have to remember to do and/or spend time doing. This is especially true for us because this is an isolated project that few work on and not all the time out of many dozen projects that otherwise take much longer than seconds to build. – Learner Mar 06 '20 at 13:13
  • (a), (b), (c), (d) and (e) are wrong, for reasons already mentioned. Otherwise, the documentation is lying and I'm wrong with the project builders I implemented and that are used in production for years. – howlger Mar 06 '20 at 13:56
  • You may have implemented usual builders as I have as well (and we have other kinds running for years as well), but not bytecode enhancers. So, yes, **documentation is lying** due to defects in there, not intentionally. And, yes, you are wrong in the scope of this problem (not necessarily other builders). – Learner Mar 06 '20 at 14:29
  • What are the Eclipse project builders you have implemented doing? Where exactly is the documentation lying? – howlger Mar 06 '20 at 15:24
  • I did a number of (source) code generation builders unlike bytecode enhancement. I noted above within my answer as to what is incorrect and/or otherwise defective, related to (6) and (7). – Learner Mar 06 '20 at 19:02
  • This is about Eclipse project builders, right? Code generators are something else (even if there is some overlap). I asked you where can it be found in the documentation, not where you claimed it. So please just add a link to the documentation you are referring to (for example, if you tell _"does not work as advertised"_, tell also what exactly is where exactly advertised). Or if there is a bug report, link to it or tell the bug number. Eclipse is open source, so let's fix what's wrong. – howlger Mar 06 '20 at 20:07
  • They are both code generators (based on annotations and other data from the source) and Eclipse project builders (because the same things need to work not only from command line but also in Eclipse). In some cases the outputs are `*.java`, in others other resource files, both needed by downstream projects imported into Eclipse. The documentation I referenced for the checkbox is clear and right in there in Eclipse, just click the question mark. I did not search for bug reports for all issues but I did see at least one **still open** and active. When I find more time I will comment there. – Learner Mar 07 '20 at 01:30
  • You claim the documentation is lying without being able to tell where. Now, you talk about a not yet fixed bug without telling which one. An Eclipse project builder may or may not generate code. The checkbox does exactly what its label says: if disabled, changes in the Java output folders will be ignored; if enabled, the corresponding `.java` file will be recompiled. – howlger Mar 07 '20 at 13:41
  • What makes you assume that project builders have in(put) and output folders or changes? They didn't. All project builders are triggered on every project change (edits, deletions, additions of files or folders, including file/folder attributes and timestamps). A change made by a project builder is also a change that triggers all project builders again. Infinite building indicates that you have not understood this concept. So please stop yelling that the documentation is lying, that there is a defect or that everyone else doesn't get it (see [code of conduct](https://stackoverflow.com/conduct)). – howlger Mar 07 '20 at 16:53
  • Wow! I see that you have a high score here but you seem to rely on being right every time even when you're not. "Relevant resources" = inputs. What needs to be refreshed = output. I did mention documentation. I also said I will reference the defects back. I saw them but didn't bookmark them and have spent days working around them. I need some actual rest. – Learner Mar 07 '20 at 18:29
  • Here's **ONE**: https://bugs.eclipse.org/bugs/show_bug.cgi?id=417735 – Learner Mar 07 '20 at 18:32
  • Don't fight those who are trying to help you, fight the problem. The bug report is not related and more than five years of inactivity indicates that the bug no longer exists in recent versions. A project builder gets the list of changed resources and can use this list to decide whether to act or not. In the Ant project builder this is done via _relevant resources_. In case the workspace preference _Refresh using native hooks or pooling_ is disabled, you have to set the resources to refresh upon completion. Otherwise, Eclipse would not see the changes Ant made directly on the file system. – howlger Mar 12 '20 at 00:33