Skip to content

Commit d3f2ba5

Browse files
Add generator for Eclipse JDT project files (#5614)
# Summary This pull requests provides an additional generator for the Mill Build Tool that is targeted towards working with the Eclipse IDE. There is currently no direct support for the Mill Build Tool inside the Eclipse IDE nor any maintained plug-in for Scala (as the language the build files are written in) or Kotlin, and therefore this generator is only targeting Java projects (for now?). This references #3372 to enable users of the Eclipse IDE to generate projects based on the [Java Development Tools (JDT)](https://projects.eclipse.org/projects/eclipse.jdt). The goal is to have a working generator, aggregating the Java Mill Modules (and the corresponding Test Modules) into logical Eclipse projects as good as possible. This does not provide support for the Mill build files itself (or support for Scala) since it relies on the [Build Server Protocol (BSP)](https://build-server-protocol.github.io) for which no supporting plug-in yet exists in the Eclipse IDE. # In-depth information In order to generate Eclipse project files, this generator searches for all Java (Test) Modules inside the build/project structure and tries to aggregate them together. This is contrary to the existing IntelliJ IDEA Project generator that results in one big project - Eclipse has one workspace with multiple projects created based on the modules. The aggregation follow the approach of having a module with production code and its direct corresponding test code that will become one Eclipse project. If there is no connection between production and test code found by the generate, a separate Eclipse project is created. Dependencies between modules are correctly translated to inter-project dependencies, source folders are correctly resolved (and marked whether they have production or test code in them) and other external dependencies (e.g. libraries from Maven or local ones) added to the Eclipse project. To make the generator more user-friendly when invoked from the command line, logs are provided showing all the steps as well as every Eclipse project created with its name and location - so it is easier later for users to locate them and import them into the workspace. A synthetic non-Java Eclipse project is created for the root Mill build file (`build.mill`) in case no module was located in this folder. This way users can also access the build script using a project inside the Eclipse workspace if they like despite the fact that the Eclipse IDE, as mentioned above, provides no active support for the build tool, the build scripts and/or the Scala programming language. The file will be handled like a plain text file for now. As this generator is not 100% failsafe for every corner case, these generated files should be usable and if not, then they can be adjusted from inside the Eclipse IDE - they are not the holy grail and users might want to tweak them as they wish anyway. This also relates to logic from the Mill build that is not translated, like launcher configurations for applications or tests, these need to be configured manually from inside the Eclipse IDE (these configurations rely on separate files not linked to the ones generated by this generator). # Testing The generator was tested against complying example Java projects already present inside this repository. Additionally, integration tests were added for the generator to cover the supported (corner) cases and to point out the not supported cases (e.g. Scala / Kotlin projects). This way upcoming breaking changes to the API could be found as early as possible. ## Production testing Additionally to automatic tests running on the CI and manual tests against example projects and simple, created ones, this was also tested with a non-trivial open source project. I choose [swaldman/c3p0](https://github.com/swaldman/c3p0) due to the fact that its [module structure and build file](https://github.com/swaldman/c3p0/blob/0.11.x/build.mill) are a bit more complex with the following caveats: - generated sources outside of a module directory (will become a linked resource inside Eclipse) - test modules relying on Junit 5 (tests should start from within the Eclipse IDE) - arguments for the test modules (that won't be picked up by the generator intentionally) - source folders shared between modules (`src-proxy-interface`) ### Test plan 1. Checkout this branch on the latest commit locally 2. Download the [repository](https://github.com/swaldman/c3p0) on disk 3. From this local repository, run the following command > ./mill dist.run <path to swaldman/c3p0 repository> mill.eclipse/ <img width="1024" height="553" alt="Mill-Test-1" src="https://github.com/user-attachments/assets/3ec00ff0-f72f-4754-8c8a-2709943ce6dd" /> 4. Validate the output is correct and similar to the one in the screenshot 5. Open **Eclipse IDE for Java Developers** with a new workspace, import the projects as shown below (should be 4) <img width="838" height="614" alt="Mill-Test-2" src="https://github.com/user-attachments/assets/964729c8-9e20-4a3d-a963-eee68f49f4a3" /> <img width="838" height="765" alt="Mill-Test-3" src="https://github.com/user-attachments/assets/25f75acf-6ded-45db-927b-dd6fb201ea78" /> 6. After importing the project, await the projects to be build automatically by the IDE (happens in the background) and then check that there is no problem shown, both in the *Package Explorer* and *Problems* view: <img width="378" height="509" alt="Mill-Test-4" src="https://github.com/user-attachments/assets/08ccfb1a-2219-4275-be08-8c22ca856d80" /> 7. To make sure that everything works as expected, we run one of the Junit tests in the project `c3p0` at the location `test/src -> com.mchange.v2.c3p0.test.junit -> MarshallUnmarshallDataSourcesJUnitTestCase.java -> testRefDerefRoundTrip()` via the context menu <img width="946" height="844" alt="Mill-Test-5" src="https://github.com/user-attachments/assets/17a35343-0350-47c2-8849-65a320b903ae" /> <img width="1022" height="788" alt="Mill-Test-6" src="https://github.com/user-attachments/assets/87819baa-5614-45c3-8458-ccef1411002d" /> 8. The *JUnit* view should open somewhere in the workbench and show that the test was successful, the *Console* view might also come to the foreground showing output. I have no idea what this tests tho. Not all tests run successfully from the IDE due to configurations for the test arguments in the `build.mill` - see this limitation above! <img width="376" height="178" alt="Mill-Test-7" src="https://github.com/user-attachments/assets/6e9da3ce-7647-4f01-8b25-91f3f14e8aba" /> 9. One last check: Making sure that re-generating the Eclipse project files work after applying changes to the Mill build script. For that, modify [this line](https://github.com/swaldman/c3p0/blob/0.11.x/build.mill#L2) to another Java version, e.g. 17 and run the command again > ./mill dist.run <path to swaldman/c3p0 repository> mill.eclipse/ <img width="1437" height="733" alt="Mill-Test-8" src="https://github.com/user-attachments/assets/1d2c58c4-065f-42ef-88da-91a66b9cc2a0" /> 10. We have to refresh the projects inside the Eclipse workspace, as seen in the screenshots below. After that, they should show that they now rely on Java 17: <img width="474" height="727" alt="Mill-Test-9" src="https://github.com/user-attachments/assets/d2731caf-7c62-4562-b526-52e95a7f31f3" /> <img width="376" height="178" alt="Mill-Test-10" src="https://github.com/user-attachments/assets/7a35ceb7-2117-4864-a942-a0b3a9908717" /> This should conclude the testing on a non-trivial open source project relying on the Mill Build Tool successfully! # Further improvements There might be further improvements to this generator that are out of the scope of this pull requests. These include: - Creating a rudimentary Eclipse plug-in providing support for importing Mill builds (by calling the generator in the back) and supporting at least syntax highlighting to the build files, maybe context menu options to "re-generate" the files etc. - Finetuning the Eclipse project Java source / target versions by also looking at the `JavaModuleApi#javacOptions` or something similar - creating project-based filters to hide nested projects inside the IDE in the *Project Explorer* or *Package Explorer* (this is not that trivial sadly) I'm open to contribute any of these if possible and time (and life) permits 😄 --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 1bf125f commit d3f2ba5

File tree

39 files changed

+1510
-4
lines changed

39 files changed

+1510
-4
lines changed

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,11 @@ node_modules/
1919
mill-assembly.jar
2020
mill-native
2121
*.mill.orig
22-
*.mill.rej
22+
*.mill.rej
23+
24+
# Eclipse JDT related files
25+
.project
26+
.classpath
27+
.settings/
28+
example/**/bin/
29+
integration/**/bin/

core/api/daemon/src/mill/api/daemon/internal/JavaModuleApi.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package mill.api.daemon.internal
22

33
import mill.api.daemon.internal.bsp.BspJavaModuleApi
4+
import mill.api.daemon.internal.eclipse.GenEclipseInternalApi
45
import mill.api.daemon.internal.idea.{GenIdeaInternalApi, GenIdeaModuleApi}
56
import mill.api.daemon.internal.{EvaluatorApi, ModuleApi, TaskApi, UnresolvedPathApi}
67

@@ -38,6 +39,11 @@ trait JavaModuleApi extends ModuleApi with GenIdeaModuleApi {
3839
*/
3940
private[mill] def genIdeaInternal: () => GenIdeaInternalApi
4041

42+
/**
43+
* Internal access to some GenEclipse helper tasks. These are used when constructing the necessary information to
44+
* create a resolved module. This in turn will be used later for creating the actual Eclipse JDT project files!
45+
*/
46+
private[mill] def genEclipseInternal: () => GenEclipseInternalApi = null
4147
}
4248

4349
object JavaModuleApi
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package mill.api.daemon.internal
2+
3+
trait KotlinModuleApi extends JavaModuleApi
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package mill.api.daemon.internal.eclipse
2+
3+
import mill.api.daemon.internal.TaskApi
4+
5+
/**
6+
* For Eclipse we have to get the information for every Mill Module despite the fact that some
7+
* will be aggregated together into one Eclipse JDT project. This will be the case for Test
8+
* Modules that will be merged together with their parent (production code) Java Module if
9+
* possible.
10+
*/
11+
trait GenEclipseInternalApi {
12+
private[mill] def genEclipseModuleInformation(): TaskApi[ResolvedModule]
13+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package mill.api.daemon.internal.eclipse
2+
3+
import mill.api.daemon.Segments
4+
import mill.api.daemon.internal.JavaModuleApi
5+
6+
import java.nio.file.Path
7+
8+
/**
9+
* The relevant information for Eclipse based on a resolved module. This does not intentially mean that this modules
10+
* will be a separate Eclipse project or part of another one. But in both cases it contains the necessarz information
11+
* to either create a standalone Eclipse project or be merged with an existing one.
12+
*
13+
* We use paths for all the module dependencies since at the point of creating an object of this class it is not final
14+
* what the name is. Also, in case modules have the same name, it will be later changed inside the generator as no two
15+
* projects insidea Eclipse workspace can have the same name!
16+
*
17+
* @param segments ???
18+
* @param module the actual Mill module that will be used to link this in the Eclipse files generator
19+
* @param allSources paths to all source + resources folders (includes generated source)
20+
* @param allModuleDependencies paths to all module dependencies, ignoring compile / runtime
21+
* @param allLibraryDependencies paths to all libary dependencies, ignoreing compile / runtime
22+
*/
23+
final case class ResolvedModule(
24+
segments: Segments,
25+
module: JavaModuleApi,
26+
allSources: Seq[Path],
27+
allModuleDependencies: Seq[Path],
28+
allLibraryDependencies: Seq[Path]
29+
)

integration/ide/bsp-server/resources/snapshots/build-targets-output-paths.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,18 @@
104104
{
105105
"uri": "file:///workspace/.bloop/",
106106
"kind": 2
107+
},
108+
{
109+
"uri": "file:///workspace/.project/",
110+
"kind": 2
111+
},
112+
{
113+
"uri": "file:///workspace/.classpath/",
114+
"kind": 2
115+
},
116+
{
117+
"uri": "file:///workspace/.settings/",
118+
"kind": 2
107119
}
108120
]
109121
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package build
2+
3+
import mill.*, kotlinlib.*
4+
5+
object `package` extends KotlinModule {
6+
def kotlinVersion = "1.9.24"
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package production
2+
3+
fun main(args: Array<String>) = println("Hello World")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package build
2+
3+
import mill.*, javalib.*
4+
5+
object project extends MavenModule {
6+
object test extends MavenTests, TestModule.Junit4
7+
object integration extends MavenTests, TestModule.Junit5
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package production;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
public class MainIntegrationTest {
6+
@Test
7+
public void testMain() {
8+
Main.main(new String[] {});
9+
}
10+
}

0 commit comments

Comments
 (0)