project.buildDir and eclipse.classpath.defaultOutputDir are inconsistent by default, which cause confusion for beginners

hi folks,

I’m new to Gradle, when I was first using Buildship, I imported my project into Eclipse with Buildship, it used the default output dir target/bin, things works so far.
However, I need to run some task before running my tests (or launching the main class), so I executed the task on the Gradle Task viewer, I thought the output of the task (generating some properties file) not under target/bin, but it’s under build, which is the default value of project.buildDir.

after making them consistent, my problem solved.

here is my proposal for improving this:

  1. use project.buildDir is no custom configuration available for eclipse plugin
  2. warn on the project if the value of project.buildDir and eclipse.classpath.defaultOutputDir are inconsistent.

the other advantage is, this will make thing simple when implement the feature “Run tasks on Eclipse auto-build” in future.

Hi Nick,

using a different classes output directory for Eclipse was a deliberate decision, because Eclipse uses a different compiler than Gradle. Letting both write to the same directory would lead to both being constantly out of date.

You can register a custom source folder for your generated resources. This will work equally well for Gradle and Eclipse.


def generatedResources = "$buildDir/generated-resources/main"

task generateMyResources {
  doLast {
    def properties = new File(generatedResources, "myGeneratedResource.properties")
    properties.parentFile.mkdirs()
    properties.text = "message=Stay happy!"
  }
}

sourceSets.main.resources.srcDir generatedResources //Eclipse will pick it up as a source folder
processResources.dependsOn(generateMyResources)

Cheers,
Stefan

If you don’t want it to show up as a source folder, you can also go with this different solution, which puts it on the Eclipse classpath as a library directly. This is a bit more efficient, because Eclipse will not copy it around a second time.

def generatedResources = file("$buildDir/generated-resources/main")

task generateMyResources {
  doLast {
    def properties = new File(generatedResources, "myGeneratedResource.properties")
    properties.parentFile.mkdirs()
    properties.text = "message=Stay happy!"
  }
}

sourceSets.main.output.dir(generatedResources, builtBy: generateMyResources)

eclipse.classpath.file.whenMerged {
	entries << new org.gradle.plugins.ide.eclipse.model.Library(fileReference(generatedResources))
}
1 Like

Also, you should never generate directly to project.buildDir, because that’s the directory that contains all build output. Instead your generator should generate to a directory below the build dir, like shown in my examples above.

hi Stefan
thanks for the detail reply.
here is my other case, my spring configuration xml contains place holders like ‘${cacheVersion}’ and I like to replace it before run the test.
with Maven Eclipse Plugin, it auto replace the place holder with maven properties when build the project in Eclipse, this is done with defining m2e lifecycle-mapping plugin for Maven in the pom.xml, see more detail of the corresponding M2E code: https://github.com/eclipse/m2e-core/blob/master/org.eclipse.m2e.core/src/org/eclipse/m2e/core/internal/builder/MavenBuilder.java#L107-L115

so, I think now that M2E can accomplish the task, the same should be with Buildship

here is the relate pom snippets:

  <resource>
    <directory>src/main/resources</directory>
    <filtering>true</filtering>
  </resource>

    <plugin>
        <groupId>org.eclipse.m2e</groupId>
        <artifactId>lifecycle-mapping</artifactId>
        <configuration>
            <lifecycleMappingMetadata>
                <pluginExecutions>
                        <pluginExecutionFilter>
                            <groupId>org.codehaus.gmavenplus</groupId>
                            <artifactId>gmavenplus-plugin</artifactId>
                            <versionRange>[0.0,)</versionRange>
                            <goals>
                                <goal>execute</goal>
                            </goals>
                        </pluginExecutionFilter>
                        <action>
                            <execute>
                                <runOnIncremental>true</runOnIncremental>
                            </execute>
                        </action>
                    </pluginExecution>
                </pluginExecutions>
            </lifecycleMappingMetadata>
        </configuration>
    </plugin>

      <plugin>
          <groupId>org.codehaus.gmavenplus</groupId>
          <artifactId>gmavenplus-plugin</artifactId>
          <executions>
              <execution>
                  <id>set-custom-artifact-version</id>
                  <phase>initialize</phase>
                  <goals>
                      <goal>execute</goal>
                  </goals>
                  <configuration>
                      <properties>
                          <property>
                            <name>version</name>
                            <value>${project.version}</value>
                          </property>
                      </properties>
                      <scripts>
                          <script><![CDATA[
import org.osgi.framework.Version
v = version
int snapIdx = v.indexOf("-SNAPSHOT")
if (snapIdx > 0) {
     v = v.substring(0, snapIdx)
}

Version ver = Version.parseVersion(v)
stripedVersion = ver.major + "." + ver.minor
project.properties.setProperty('cacheVersion', stripedVersion)
]]>
                          </script>
                      </scripts>
                  </configuration>
              </execution>
          </executions>
      </plugin>

This would require task execution on auto build, a feature that we have not yet implemented.

yes, I know that.
my point is M2E use the same buildDir as maven, aka the default maven project layout.
so if we go with the same way as M2E for buildship, that make eclipse.classpath.defaultOutputDir=target/classes, and project.buildDir=target (which will build the class files to ${project.buildDir}/classes so that the task execution on auto build can processing the file to same output, this can make thing easier for beginner.

again, on the implementation, we can borrow the idea of M2E on how to avoid conflict with output generated by Eclipse Java Builder and Maven Project Builder, I think it’s matter of the order of execution.

Gradle separetes classes and resources, there is no single output dir for them like in Eclipse.

Quoting from above:

using a different classes output directory for Eclipse was a deliberate decision, because Eclipse uses a different compiler than Gradle. Letting both write to the same directory would lead to both being constantly out of date.

The downside of this is that you end up with two sets of built classes. Eclipse debug works with one set but your production build is created from the other set. Both confusing and potentially the cause of additional work to sort out any differences between the two ways of building. Not desirable.

You simply can’t use the same directory for both. Eclipse will throw classes for all languages together, plus resources. Gradle carefully separates them for proper cacheability and incremental builds.

Plus, even if you use the same folder, it’s still a different compiler, so the difference is still there. Any confusion is purely due to the Eclipse compiler behaving different from javac. The only proper way to fix this is what IDEA already allows: Delegate the build to Gradle completely.

FWIW, I really like the split output directory design. It’s worth it because Eclipse’s incremental compiler is just so darn good & fast. Having code changes in large modules compile virtually instantly (and possibly hot-swapped into a debugged application) is a huge benefit for me. It’s one reason I stick with Eclipse despite various benefits IDEA would bring.