JetBrains / gradle-idea-ext-plugin

Plugin to store IJ settings in gradle script
Apache License 2.0
234 stars 39 forks source link

Some type of equivalent to withXml or more of a one-to-one mapping between DSL and IDEA config #36

Open denuno opened 6 years ago

denuno commented 6 years ago

The withXml feature of the Gradle generated configs is great as it exposes the low-level stuff for folks willing to look at a bit of IDEA configuration XML. It would be awesome if the DSL could do something similar for peeps willing to look at the IDEA configuration sources.

I've looked at the plugin source but have not looked at the source on the IDE side, so I'm not sure how things have been done, or what is possible, but it would be swell if there was more of a one-to-one mapping, and an easy way to expand on what's already here. For instance I thought I might be able to extend the Facet class within my buildSrc to add some ad-hoc ones (Hibernate for instance), but I was unsuccessful with a little go at it (can't remember why offhand, but it made me abandon that approach pretty quick, whatever it was).

When I say one-to-one, I mean that the IDEA Spring configuration takes a fileset, but the DSL only supports a single file, and TestNG has the working directory but not the output directory, nor some of the other options...

Ultimately it would be good to be able to set anything that one can set in the IDEA config via the DSL, and without looking at the sources (so said famously) it seems like there should be a way to avoid a bunch of duplication, or needing to "manually" per se bridge the two. Similar to how withXml is just a communication channel to the configuration versus an implementation per se.

Without looking at the other side of the model I don't know what's possible, though from the plugin model it looks like it's just maps being passed, so maybe something generic could be done, but ¯_(ツ)_/¯.

nskvortsov commented 6 years ago

I definitely agree the plugin could use some generic approach to IntelliJ Idea settings. It will continue looking for the solution. On the other hand, one thing is for sure: the plugin will stay as far away from withXml or any other direct means of access to Idea settings storage, as possible.

denuno commented 6 years ago

I still haven't looked at the Idea settings storage model, if it's horrible, then an abstraction that could hopefully maybe even eventually replace it or some such sounds swell!

If it's solely about adding a level of abstraction, I'd tell people a "don't use the internal classes, they may change without warning!" type of deal, but wouldn't make it impossible for them to do so-- for whatever that's worth. I'd rather provide a sub-optimal route than no route, basically, but I get wanting to avoid exposing internals.

Right now using withXml-ish (via Groovy, just directly hitting the XML files during the Gradle configuration stage on first run, if running from Idea) combined with this plugin, is how I've automated the whole shebang, and it's actually working alright I think, with the one caveat of the user having to close and reopen the project after the import to see all the run configurations/application servers/datasources/etc..

I don't mind XML half as much as I used to, thanks to a lovely Groovy DSL for it, so here's to hoping for a lovely Idea configuration DSL I can use to eventually replace all the XML DSL bits with!

Looking good so far-- keep up the good work!

guai commented 5 years ago

@nskvortsov, that is bullshit. There only two ways this plugin can become useful someday. 1) You wrap everything (EVERYTHING!!!) one can configure in Idea's gui with dsl. With current progress speed it will happen somewhere between 100 and 200 years from now. 2) You provide low-level capabilities. Its actually not that hard to save Idea's xml configs, change something in gui, diff and reapply these changes from groovy. It does the job. And no special skills needed - just xml, diff, groovy. And there are custom plugins for idea which also need to be configured.

You really should have start with this feature and then implement everything else on top of it.

denuno commented 5 years ago

Yeah, an abstraction only makes sense if there's going to be more than one application-- like say this plugin was also going to be used to configure MS Code and Eclipse as well somehow.

As this is just for IDEA, and as @guai mentions, there are 3rd party plugins people need to configure, abstracting the configuration editing probably makes more sense than writing an API for just a few things and leaving no alternative but to Do Something Else for the rest.

Toss in a hook or two for stuff that needs to be run before other stuff, like JDK configuration (https://github.com/JetBrains/embeddedProjectJdk was a great example-- thanks for pointing that out @nskvortsov!) and be done with it. Turns out all that is needed to do this is there already.

Using this extension was adding a lot of memory/processing overhead for some reason (could have been how I was using it, as I have multiple modules/sub-projects/whatever, whereas maybe most folk only have one or something?), so I ended up going back to just syncing the XML using Groovy.

Going the XML route allowed me to configure everything I needed to configure, and I was able to move on. Probably should have just stuck with the old Gradle DSL and IPR/etc. files, but the main reason I ended up at this project was to move to directory-based configurations, and I didn't want to give that up, as breaking the configs up a bit is a step in the right direction.

Ultimately there's a lot of work that needs to be done in how IDEA stores its configuration information. Too many places use hard-coded values versus tokens, lists are randomly re-ordered, etc., so it's neigh impossible to commit the configs to a VCS due to all the churn (at least with modules that leverage dependency management versus IDEA's library stuff). Ignoring the .idea directory and generating it all works a lot better actually. It's nice to be able to just blow things away to get back to a known state instead of having the pet configurations. But I digress. :smiley: If the goal of the API is to avoid pet configs, more power to you, but yeah something low level is needed as I had to abandon this extension's route.

vitgorbunov commented 5 years ago

Maybe someone will find useful the following workaround - using gradle task in taskTriggers to modify xml configuration. For some configuration I replace entire xml file, for some - just modify some node. But I do miss withXml analog as well.

task modifyIdeaXml {
    doLast {
        // project
        configureCodeStyle()
        modifyVCSConfig()
        modifyCompilerConfig()

        def wsFile = file('.idea/workspace.xml')
        def wsRootNode = new XmlParser().parse(wsFile)
        modifyRunConfigurations(wsRootNode)
        modifyWorkspaceCompilerConfig(wsRootNode)
        wsFile.text =  XmlUtil.serialize(wsRootNode)
    }
}

idea {
    project {
        settings {
            encodings {
                encoding = 'UTF-8'
            }

            taskTriggers {
                afterSync tasks.getByName("modifyIdeaXml")
            }
        }
    }
}

def configureCodeStyle () {
    copy {
        from 'gradle/idea/codeStyles/Project.xml'
        into '.idea/codeStyles/'
    }

    // idea restores file if we just overwrite it by copy, so need to replace content instead
    file('.idea/codeStyles/codeStyleConfig.xml').text = file('gradle/idea/codeStyles/codeStyleConfig.xml').text
}

def modifyRunConfigurations(def wsRootNode) {
    def runManagerNode = wsRootNode.component.find {it.@name == 'RunManager'}
    def junitDefaultNode = runManagerNode.configuration.find {it.@type == 'JUnit' && it.@default == 'true'}
    junitDefaultNode.method.option.find {it.@name == 'Make'}?.replaceNode {}
}

def modifyCompilerConfig() {
    file('.idea/compiler.xml').text = file('gradle/idea/compiler.xml').text
}

def modifyVCSConfig () {
    file('.idea/vcs.xml').text = file('gradle/idea/vcs.xml').text
}

def modifyWorkspaceCompilerConfig(Node wsRootNode) {
    wsRootNode.component.find {it.@name == 'CompilerWorkspaceConfiguration'}?.replaceNode {}
    def compilerNode = new Node(wsRootNode, 'component', [name: 'CompilerWorkspaceConfiguration'])
    new Node(compilerNode, 'option', [name: 'CLEAR_OUTPUT_DIRECTORY', value: 'false'])
    new Node(compilerNode, 'option', [name: 'REBUILD_ON_DEPENDENCY_CHANGE', value: 'false'])
}
mikejhill commented 4 years ago

@vitgorbunov That is hugely useful to our team -- thank you. I understand the author's desire to avoid withXml and stick to IntelliJ's supported external system config options, but we have many small bits of .idea customization that are not supported but that we need to share between devs. E.g., I haven't yet found a supported way to use the Eclipse compiler (supported compiler configs) or set the JavaScript language level. We currently store these files in VCS (compiler.xml and misc.xml), but this makes IDE updates a pain since we don't all update at the same time and users on different IDE versions constantly add churn to these files. The afterSync phase hook you use is a perfect workaround for us. With a bit of work, I made a withXml equivalent and a corresponding DSL (Kotlin only) that works well for us for file customization. It's not prepped to be shared outside our org yet, but if there's interest I can clean up what I have and share.

eugene-bk-eng commented 4 years ago

@mikejhill

Mike, this would be very useful to us as well. We use withXml extensively to customize resulting XML file.

@nskvortsov I do not understand why it is difficult to implement withXml hooks in extension plugin and users have to resort to writing hacks. My organization is a licensed client and we would love to get this feature. Gradle has a wide adoption in the industry and having better support for Gradle in the IDE is only going to benefit IntelliJ IDE. At the moment, we disabled Gradle in IJ because without withXml{} support, plugin can't be used.