Skip to content
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

Added support for Maven POM sorting/formatting #946

Merged
merged 14 commits into from
Sep 30, 2021
Merged

Conversation

tisoft
Copy link
Contributor

@tisoft tisoft commented Sep 24, 2021

Adds the sorting mechanism of the sortpom-maven-plugin.

While the sortpom-maven-plugin is a maven plugin in itself, I like all my code formatting in one plugin, so this PR integrates the pom sorting from that plugin. Configuration options are the same as in the upstream project.

@tisoft
Copy link
Contributor Author

tisoft commented Sep 24, 2021

Documentation is still missing, would like to know if this is something you want inside spotless, before I do the tedious documentation work. 😄

Copy link
Member

@nedtwigg nedtwigg left a comment

Choose a reason for hiding this comment

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

It would be great to add this, but the third-party dep needs to be resolved dynamically via JarState. If every formatter in lib and lib-extra just added itself as a dependency, we would have an absolute nightmare of transitive conflicts, and we would have to publish a new plugin every few days as one or another plugin releases a new version.

@@ -16,6 +16,8 @@ dependencies {
implementation "com.googlecode.concurrent-trees:concurrent-trees:2.6.1"
// used for xml parsing in EclipseFormatter
implementation "org.codehaus.groovy:groovy-xml:3.0.9"
// used for pom sorting
implementation 'com.github.ekryd.sortpom:sortpom-sorter:3.0.0'
Copy link
Member

Choose a reason for hiding this comment

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

This dep needs to be resolved like this:

this.jarState = JarState.from(MAVEN_COORDINATE + version, provisioner);

Comment on lines 98 to 132
return input -> {
// SortPom expects a file to sort, so we write the inpout into a temporary file
File pom = File.createTempFile("pom", ".xml");
pom.deleteOnExit();
try (FileWriter fw = new FileWriter(pom)) {
fw.write(input);
}
SortPomImpl sortPom = new SortPomImpl();
sortPom.setup(new SortPomLogger() {
@Override
public void warn(String content) {
logger.warning(content);
}

@Override
public void info(String content) {
logger.info(content);
}

@Override
public void error(String content) {
logger.severe(content);
}
}, PluginParameters.builder()
.setPomFile(pom)
.setFileOutput(false, null, null, false)
.setEncoding(encoding)
.setFormatting(lineSeparator, expandEmptyElements, spaceBeforeCloseEmptyElement, keepBlankLines)
.setIndent(nrOfIndentSpace, indentBlankLines, indentSchemaLocation)
.setSortOrder(sortOrderFile, predefinedSortOrder)
.setSortEntities(sortDependencies, sortDependencyExclusions, sortPlugins, sortProperties, sortModules, sortExecutions)
.setTriggers(false)
.build());
sortPom.sortPom();
return IOUtils.toString(new FileReader(pom));
Copy link
Member

Choose a reason for hiding this comment

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

And the jar should be called using reflection. You can do it raw as we do for google-java-format,

or it might be worth incorporating some pieces of jOOR to make this easier, or if you're really fluent with Gradle you could do it with #524

@tisoft tisoft force-pushed the sortpom branch 2 times, most recently from 14ee011 to b7958a9 Compare September 26, 2021 06:32
@tisoft
Copy link
Contributor Author

tisoft commented Sep 26, 2021

@nedtwigg I played around with #524. Classloading is a bit awkward... Do you have any better idea?

Copy link
Member

@nedtwigg nedtwigg left a comment

Choose a reason for hiding this comment

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

I think the way that you added a sortPom sourceSet is great. I don't understand why JarState::getClassLoader wasn't enough, I don't think you need the serialization tricks nor to make a new kind of classloader. I might be wrong though.

The classloader you already get from JarState is built like so:

.computeIfAbsent(serializedKey, k -> new FeatureClassLoader(state.jarUrls(), this.getClass().getClassLoader()));

https://github.com/diffplug/spotless/blob/main/lib/src/main/java/com/diffplug/spotless/FeatureClassLoader.java

I might be wrong, but I think that you should be able to pass objects from SortPomStep to SortPomFormatterFunc without serializing them. This stuff is tricky, and I'm very willing to believe that I'm wrong.

If the serialization stuff really is necessary, then we should either:

  1. add them as API's onto JarState so that the code inside SortPomStep is easy to read, and the serialization boilerplate happens inside JarState
  2. this would be a bummer, but maybe FeatureClassLoader needs to be public API, and the serialization boilerplate can go there.

But ideally it isn't necessary in the first place!

lib/build.gradle Outdated Show resolved Hide resolved
@tisoft
Copy link
Contributor Author

tisoft commented Sep 27, 2021

I'll have another go at the class loading stuff after the other two PRs are in. Can't handle all the parallelity in my brain. 🤣

@tisoft
Copy link
Contributor Author

tisoft commented Sep 28, 2021

I could get rid of the serializing deserializing, but I couldn't get rid of the DelegatingClassLoader.

The problem is the following:

  • The SortPomFormatterFunc directly references classes from the sortpom dependency
    • so we can't load it from the current ClassLoader, since that doesn't know about the sortpom dependencies
    • we can't load it from the JarState ClassLoader, since that doesn't know about SortPomFormatterFunc, SortPomState and FormatterFunc
    • So we need a ClassLoader, that knows about both
  • My first idea was to change the FeatureClassLoader to allow loading SortPomFormatterFunc from the buildToolClassLoader
    • This would attachSortPomFormatterFunc to the buildToolClassLoader
    • As soon as the constructor is retrieved via reflection, the dependencies of the class will be tried to load with the buildToolClassLoader
    • This fails, since the buildToolClassLoader doesn't know about the sortpom package, since it is effectively a parent from the `FeatureClassLoader, and classes from it's children are invisible
  • So the SortPomFormatterFunc needs to be attached to a ClassLoader, that knows about the sortpom package: the DelegatingClassLoader
    • It uses the current ClassLoader and the FeatureClassLoader as sources
    • But it doesn't load the classes directly from them (since they would be attached to the source classloaders, bringing us back to the previous problem)
    • Instead it loads the classes as resources, and then calls define class, to attach them to the DelegatingClassLoader
    • Now SortPomFormatterFunc and all classes from the 'JarState' are loaded by the same Classloader and can see each other and all is fine...
  • ... except that now SortPomState and FormatterFunc are also attached to DelegatingClassLoader and the funny De-/Serializing of the state and the call to 'apply' via reflection is needed
  • That's why the DelegatingClassLoader now excludes those 2 classes from the custom classloading process and just returns the Class objects already loaded
  • Which only works, if the SortPomState and its member fields are public

I hope this helps to understand, why I did it in this way. If there is an easier or more elegant solution, I'm all for it. 😄

- SortPomCfg needs no dependencies, just Strings and booleans
- SortPomCfg now has the default values, which simplifies plugin-maven/.../SortPom.java
- SortPomStep does the JarState and classloader stuff
…ineClass`, and `com.diffplug.spotless` with `buildToolClassLoader`. This unifies FeatureClassLoader with DelegatingClassLoader.
…`SortPomFormatterFunc` needs `src/sortPom/java`.
@nedtwigg
Copy link
Member

Well I broke eclipse, but I fixed PomStep. Does this approach look good to you @tisoft? You got all the hard parts working, I just massaged things around with an eye towards other parts of Spotless adopting this same "glue" approach.

Comment on lines +34 to +41
*
* For `com.diffplug.spotless.glue.`, classes are redefined from within the lib jar
* but linked against the `Url[]`. This allows us to ship classfiles which function as glue
* code but delay linking/definition to runtime after the user has specified which version
* of the formatter they want.
*
* For `"org.slf4j.` and (`com.diffplug.spotless.` but not `com.diffplug.spotless.extra.`)
* the classes are loaded from the buildToolClassLoader.
Copy link
Member

@nedtwigg nedtwigg Sep 30, 2021

Choose a reason for hiding this comment

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

@fvgh, just FYI, we finally have a way to ship glue code without requiring all deps of that glue code. It works like so:

  • com.diffplug.spotless.glue. -> we define classes from the jars, which requires linking against com.diffplug.spotless from buildToolClassLoader
  • we make an exception for com.diffplug.spotless.extra, which we always load from the jars as (we have in the past)
  • we also load org.slf4j. from buildToolClassLoader (as we have in the past)

This will allow us to replace a lot of manual reflection code, ala #524.

@nedtwigg
Copy link
Member

@tisoft I think all we need now are changelog updates and documentation. Another fantastic PR, thanks very much! You were the savior to come and finally figure out how we can do #524, which will make the project much easier to read and contribute to.

@tisoft
Copy link
Contributor Author

tisoft commented Sep 30, 2021

@nedtwigg Added documentation and changelog entries.

@nedtwigg nedtwigg merged commit 5951f3d into diffplug:main Sep 30, 2021
@nedtwigg
Copy link
Member

Released in plugin-maven 2.15.0

@nedtwigg
Copy link
Member

I advise the following:

  • test in a production setting to make sure that the published artifact has the glue code
  • once it is confirmed working, submit a PR to https://github.com/Ekryd/sortpom showing how to use it with spotless

I also anticipate that you will want to add a version property to SortPomCfg, so that you can use the latest version of sortpom without needing to publish a whole new set of Spotless artifacts each time, but that can wait until it's needed.

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.

None yet

2 participants