如何在 Eclipse 中可靠地设置 post-compilation bytecode enhancement builder?

How to reliably set up post-compilation bytecode enhancement builder in Eclipse?

我需要使用一个额外的构建器来设置一个 Eclipse 项目,该构建器增强了早期构建器(最好是 Eclipse 自己的)生成的 Java 字节码。我设法让此构建器达到 运行 并正确增强 Eclipse Java 构建器输出,但几秒钟后 Eclipse 重新 运行 其 Java 构建器并重置字节码。它不会重新运行 我的增强生成器。

我的设置

我试过的替代方案

  1. 将我的构建器设置为 运行 after/during a "Clean" 的一些组合也没有成功。不确定这些具体事件与什么有关,真的。
  2. 让建造者在……之后刷新项目,但也没有——没有帮助。
  3. 尝试使用 Gradle 脚本中的以下位删除 Java 构建器(没有用 - 它会自行恢复):

    eclipse {
        project {
            file {
                whenMerged { projectFile ->
                    projectFile.buildCommands.removeAll { it.name == 'org.eclipse.jdt.core.javabuilder' }
                }
            }
        }
     }
    
  4. 尝试手动禁用 Java 构建器并让我的字节码增强构建器也自己构建文件(使用 Gradle)。这将存储具有以下内容的以下文件 org.eclipse.jdt.core.javabuilder.launch 文件...但在重新启动时构建器将重新启用:

    <?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. 我尝试(但失败了)查找是否有某些工作区文件(而不是项目文件)被更改(以及)以禁用 Java 构建器。

问题

  1. 为 post-编译字节码增强设置 Eclipse 的 "proper" 方法是什么?
  2. 是什么导致 Eclipse 重新运行 以前的构建器而不重新运行我的?
  3. 有没有办法解决 (1)?
  4. 如何可靠地禁用 Java 生成器?

有人可以帮忙吗?谢谢!

更新更多详细信息 我添加了 12 个构建器,并让它们都将输出附加到同一个日志文件以进行研究。额外的 12 个构建器仅供参考 - Java 构建器之前有 4 个,Java 和增强构建器之间有 4 个,增强构建器之后有 4 个。 12 运行 中的每一个都只在四个条件之一中出现(因此是 3x4)。排列如下:

  1. Gradle 项目生成器
  2. 1a-after-clean(运行s only After a "Clean"
  3. 1b-手动(仅 运行s 在手动构建期间
  4. 1c-auto(仅运行s 自动构建期间
  5. 1d-during-clean(仅运行s "Clean"
  6. Java 生成器
  7. 2a-after-clean(运行s only After a "Clean"
  8. 2b-手动(仅运行s 在手动构建期间
  9. 2c-auto(仅运行s 自动构建期间
  10. 2d-during-clean(仅运行s "Clean"
  11. 字节码增强生成器
  12. 3a-after-clean(运行s only After a "Clean"
  13. 3b-手动(仅 运行s 在手动构建期间
  14. 3c-auto(仅运行s 自动构建期间
  15. 3d-during-clean(仅运行s "Clean"

12 个信息构建器中的每一个都写下时间、名称和所选测试的大小 class。未增强的长度为 46243 字节。增强后变为 53338 字节长。

这里是 运行ning "Clean" 之后的日志(启用 "Build automatically"):

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

更新 2:要重现的最小示例

  1. 创建文件夹 - 随意命名。
  2. 在该文件夹中创建具有以下内容的 build.grade 文件:

    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. 也在那里创建一个src/main/java/learner/TestEntity.java如下:

    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. 执行gradle compileJava。在 ASCII 或十六进制查看器中打开生成的 build/classes/java/main/learner/TestEntity.class 二进制文件并观察其中的 $$_hibernate_write_name 之类的内容。

  5. 将此项目作为 Gradle 项目导入 Eclipse(比如 2019-12)并构建它。打开生成的 bin/main/learner/TestEntity.class 并观察其中的 none。

有些事情我 unclear/wrong 很感兴趣,但找不到相关文档来了解详细信息。以下是正确执行此操作所需了解的内容的摘要(其中一些是我从一开始就掌握的,但不是全部):

  1. Gradle/Buildship Eclipse 中的集成试图利用 Eclipse 的内部编译器。这是 Eclipse 的设计,在开发过程中有其自身的优点......以及在这样的时候的缺点 - 无法利用 external/production 构建器(在本例中为 Gradle 模型)进行构建。出于这个原因,在 Gradle 中运行的任何字节码增强(插件或非插件)在 Eclipse 中根本没有影响(除非它们做一些特定于 Eclipse 的事情)。 (我没看错)
  2. 要在 Eclipse 中执行字节码增强(不是 apt 或某些 Eclipse 插件可以做的),必须添加自定义构建器并将它们放置在默认 Java 构建器之后(手动或自动)。 (我也猜对了)
  3. 现成的 Eclipse 提供两种构建器 - "Ant" 和 "Program"。那里没有 Gradle。 "Program" kind 不能跨平台,只有Ant 可以。 Ant 可用于做事或以跨平台方式启动 Gradle(Java exec on Gradle 的 main() 方法)。 (我也猜对了)
  4. [我在这里错了] Eclipse 提供了四个 "events" 可以绑定 Ant 构建器:在 "Clean" 手动构建自动构建在"Clean"期间。我不明白他们什么时候运行。例如,我认为 "During a Clean" 运行s while clean 正在发生并且 After a "Clean" 之后是 运行,以允许自定义生成器进行自己的清理后清理。我认为如果启用了 "Build automatically" 或在 "Clean" 对话框中选中了 "Build immediately",这之后会自动构建。 情况并非如此在 "Clean" 实际上指的是构建步骤之后,不是清理,也不会跟在 *Auto Build" 步骤之后(该步骤仅 运行s 当一个保存编辑并启用 "Build Automatically"。在构建器的 *.launch 文件中,这更加明显 - After a "Clean" 实际上被调用 fullAutoManual 构建称为 autoincremental。这意味着必须设置 en enhancement builder 运行 在 "Clean" 之后 Auto BuildManual Build 并且不应在 *"Clean" 期间设置为 运行。我的错误是只将它设置为 运行 用于 Auto手动 构建。
  5. [我在这里也错了]我在构建选项相关资源中指定了工作集 选项卡用于增强构建器。我将这些资源设置为源代码(待增强)和 build.gradle(包含增强器)。由于这些在大多数构建中都不会更改,因此 Eclipse 选择不 运行 构建器。现在显而易见的 20-20 愿景事实是,此构建器的相关资源是 Java 构建器的输出二进制文件(和 build.gradle),而不是 Java 源代码。然而,这不是完全正确的选择(孤立地),因为在我们的例子中,Eclipse 以无限循环结束——它认为增强器更改了二进制文件,并且当二进制文件被设置为 运行 时已更改,运行 再次构建。我们不能完全不设置相关资源,因为这似乎意味着 "everything/anything"。增强器必须以这样的方式制作,甚至不能触及已经增强的文件 [UPDATE],这还不够。继续阅读。

我仍然不确定为什么我用来研究这个的信息构建器将他们的输出附加到公共日志文件,以便 不是按时间顺序排列的 。我只能假设这与 Eclipse 的输出缓冲和以某种方式定期写入这些文件有关。

[更新 1]

  1. [我不知道这个] Eclipse 在 Preferences -> Java -> 编译器 -> 构建 -> 输出文件夹调用“重建class个被他人修改的文件”官方描述为“指示是否class个被他人修改的文件应该重建以撤消修改。”。默认情况下它是 unchecked/off,这似乎适合这种情况。 然而,这并不像宣传的那样有效。无论设置是什么,Eclipse 的 Java Builder 都会对其输出更改(就好像它们是输入,我称之为缺陷)和 "undo the modification" 做出反应,从而导致无限构建循环。我发现这个症状被报告了很多次 - 搜索这个。使用 "correct" 设置并且没有黑客攻击 Java 构建器不断撤消修改,Eclipse 不断重新 运行 修改它们,无论设置如何都会导致无限循环。
  2. [目前似乎有效的 HACK] 除了正确设置以上所有内容外,我修改了增强器以确保两件事(可能只需要一个或两个,不确定):(a) 现有 *.class 文件 未删除 并重新创建,但 重写 和 (b) 它们的 上次修改时间改回增强前的状态。这似乎足以欺骗 Eclipse 的修改检测以跳出循环,即使文件大小不同也是如此。这是 Eclipse 2019-12 (4.14.0.v20191210-0610),它可能会停止使用任何更新。我希望他们到那时能修复无限构建循环缺陷。