Skip to content

Create first gradle-plugin prototype #1

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

Open
TheSnoozer opened this issue Apr 12, 2023 · 6 comments
Open

Create first gradle-plugin prototype #1

TheSnoozer opened this issue Apr 12, 2023 · 6 comments

Comments

@TheSnoozer
Copy link
Contributor

Create a gradle plugin similar to the git-commit-id-maven-plugin.

Inspiration could be potentially drawn from the initial draft created years ago:
git-commit-id/git-commit-id-maven-plugin#92

Requirement:

  • support as many gradle versions as possible
  • java 11 and onwards
@TheSnoozer
Copy link
Contributor Author

TheSnoozer commented Apr 30, 2023

Things left TODO:

  • make the PropertiesOutputFormat an Enum in the core module
  • make the GenerateGitPropertiesFilename a PATH and not a String in the core module
  • give the extension name a camelcase name so gitCommitId and not git_commit_id
  • check if publications produce source and javadoc, does the signing actually work? Publish to maven local I guess to test
  • implement gitlab workflow (test with gradle and different java versions)
  • think about the up-to-date checks. Especially with a focus if a change in the configuration/extension should trigger the plugin again (e.g. a change in the prefix should likely trigger the plugin again), but then other configurations like native vs jgit should not trigger the plugin when there is no change in the git repo since it is expected the same output is generated. So is there an easy way to tell out Task/Plugin that it is not up-to-date when there are certain changes in the extension configuration?
  • Rethink how the extension is currently implemented. Maybe it would be better to split up the configurations into multiple extensions. For example one output extension to configire all settings related to generating the (output) file. One filtering extension to configure the exclude and include things...do we actually need all the true and false switches? For example it is relatively trivial in gradle to skip a task. Do we need the verbose setting?
  • implement/fix remaining TODOs in GitCommitIdPluginGenerationTask
  • Does build caching also work when we expose the properties to other projects?
  • find out if the plugin is getting triggered by a clean build. Is there an automated way to trigger the plugin if a user builds a java, scala, ... project?
  • create documentation about the configuration in-depth
  • finalize readme
  • read where gradle plugins should be published to. Is it sufficient to create an initial release to maven central? Is there anything in particular we need to configure here?

@TheSnoozer
Copy link
Contributor Author

TheSnoozer pushed a commit that referenced this issue May 6, 2023
…ferently

In #1 I noted that
this plugin likely needs the publishing outlined in https://github.com/davidweber411/how-to-publish-to-maven-central-with-gradle/.
The main reason is that maven plugins are getting published to maven central.

However I now believe that especially gradle plugins may not to be configured published to maven.central.

After reading
https://docs.gradle.org/current/userguide/java_gradle_plugin.html#maven_publish_plugin
https://docs.gradle.org/current/userguide/publishing_gradle_plugins.html

it became clear that gradle plugins should use 'com.gradle.plugin-publish' for publications, instead of a 'maven-publish'.
Furthermore the GradlePluginDevelopmentExtension has a configuration to automatedPublishing.
When set to true gradle will create the relevant publication tasks.
With the extra publication added from https://github.com/davidweber411/how-to-publish-to-maven-central-with-gradle/
it made suddenly perfectly sense that gradle was giving me a warning messages that the mainJava publication was overwritten.

there is one publication for the main java component of the plugin,
but there are also pom publications for each of the apply '...' things that then reference the plugin.
That way a plugin can provide multiple plugins that can be applied, but internally gradle will 'just' reference the same jar.

From the gradle docs:
```
When the Java Gradle Plugin (java-gradle-plugin) detects that the Maven Publish Plugin (maven-publish) is also applied by the build,
it will automatically configure the following MavenPublications:

- a single "main" publication, named pluginMaven, based on the main Java component
- multiple "marker" publications (one for each plugin defined in the gradlePlugin {} block),
  named <pluginName>PluginMarkerMaven (for example in the above example it would be simplePluginPluginMarkerMaven)
```
@TheSnoozer
Copy link
Contributor Author

This plugin will support any version from gradle 5.1 and onwards.

At least the test work with every released gradle version today: https://github.com/git-commit-id/git-commit-id-gradle-plugin/actions/runs/4900878729

Quite impressive and for me it feels reasonable "good enough" if this plugin supports any version that was released after Jan 02, 2019.

@TheSnoozer
Copy link
Contributor Author

Mhh after a bit back and forth it seems quite tricky in gradle to expose the properties to the project, while having an optional output-file generation including proper UP-TO-DATE checks.

Here are the options I have tested so far:

Inside the GitCommitIdPluginGenerationTask declare an additional @Input that reflects the properties that ahave been generated. In general that was no problem to implement, one just needed to make all the extension-getter methods as public and annotate them as @Internal so the right settings of the Extensions are available during which I would guess is the configuration time of the task. However the up-to-date checking wasn't working as I wanted to.
Essentially on the first run where the plugin wasn't executed before and the execution was expected the additional @Input that reflects the properties we want to generate was null. Only after the task execution the properties had been non null. In second run, where no changes had been made to the git repository and thus the task should have claimed to be up-to-date, gradle re-executed the task since the input properties of the task had changed.

Tried to expose the generated properties using a task-extension so something along the lines of

            getExtensions().getExtraProperties()
                .set("gitProperties", properties);

The task extension is however only available when the task was executed. So when the task is deemed up-to-date no extension and thus no properties for any project are available.

Fiddeling around with Closures. On the gradle documentation and code I have seen usage of Closures for properties.
That ended up to be something like:

project.getExtensions()
            .getExtraProperties().set("gitProperties",
                new Closure<String>(this, this) {
                    @Override
                    public String toString() {
                        Properties p = getProps();
                        StringBuilder s = new StringBuilder();
                        s.append('[');
                        for (var e: p.entrySet()) {
                            s.append(e.getKey());
                            s.append(':');
                            s.append(e.getValue());
                            s.append(',');
                        }
                        s.append(']');
                        return s.toString();
                    }

                    private Properties getProps() {
                        Properties gitProps = (Properties) task.getExtensions().getExtraProperties().get("gitProperties");
                        return gitProps;
                    }

                    @Override
                    public Object getProperty(String property) {
                        return getProps().get(property);
                    }

                    public String doCall() {
                        return toString();
                    }

                    public String doCall(Object obj) {
                        return toString();
                    }

                    public String doCall(String property) {
                        return (String)getProps().get(property);
                    }

                    // Do NOT IMPLEMENT!
                    // https://issues.apache.org/jira/browse/GROOVY-1665
                    // public String doCall(Object obj1, Object obj2) {
                    // }

                    public String get(String property) {
                        return (String)getProps().get(property);
                    }

                    public String get(String property, String defaultString) {
                        return (String)getProps().getOrDefault(property, defaultString);
                    }
                });

the usage of such closure wasn't such a big deal:

task printPropTask(type: DefaultTask) {
                        outputs.upToDateWhen { false }
                        doLast {
                            // println("${marker}1: \${project?.ext?.gitProperties}${marker}")
                            // println("${marker}2: \${project?.ext?.gitProperties.get('git.commit.id.full')}${marker}")
                            // println("${marker}3: \${project?.ext?.gitProperties.get('git.commit.id.abbrev', 'EMPTY')}${marker}")
                            // println("${marker}4: \${project?.ext?.gitProperties()}${marker}")
                            // println("${marker}5: \${project?.ext?.gitProperties('git.commit.id.full')}${marker}")
                            // println("${marker}6: \${project?.ext?.gitProperties('git.commit.id.abbrev')}${marker}")
                            // println("${marker}7: \${project?.ext?.gitProperties['git.commit.id.full']}${marker}")
                            println("${marker}\${project?.ext?.gitProperties['git.commit.id.abbrev']}${marker}")
                        }
                    }

but here again I had no access to the already generated properties.

The only option I see left now is to make the generation of the properties file mandatory. The task that generates that file can certainly be seen as expensive and thus should be cached. To make the properties available to the projects and potentially to the reactor would be a second task that reads the properties file that had been generated by the generation task. That task is certainly very leightweight and thus we don't care if it's executed everytime (no caching, no up-to-date checks).

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

No branches or pull requests

1 participant