diff --git a/build-logic/plugins/build.gradle b/build-logic/plugins/build.gradle index ef5b010fbd7..d8102f6d368 100644 --- a/build-logic/plugins/build.gradle +++ b/build-logic/plugins/build.gradle @@ -62,6 +62,10 @@ gradlePlugin { id = 'org.apache.grails.gradle.grails-code-style' implementationClass = 'org.apache.grails.buildsrc.GrailsCodeStylePlugin' } + register('grailsFormat') { + id = 'org.apache.grails.gradle.grails-format' + implementationClass = 'org.apache.grails.buildsrc.GrailsFormatPlugin' + } register('groovydocEnhancer') { id = 'org.apache.grails.buildsrc.groovydoc-enhancer' implementationClass = 'org.apache.grails.buildsrc.GroovydocEnhancerPlugin' diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy index 67974e3b884..167f9521c76 100644 --- a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy +++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsCodeStylePlugin.groovy @@ -25,7 +25,6 @@ import groovy.transform.CompileStatic import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.file.Directory import org.gradle.api.plugins.quality.Checkstyle import org.gradle.api.plugins.quality.CheckstyleExtension import org.gradle.api.plugins.quality.CheckstylePlugin @@ -58,8 +57,6 @@ class GrailsCodeStylePlugin implements Plugin { // Unfortunately, the codenarc plugin is still using a non-lazy property. // Rather than rewrite the plugin to use afterEvaluate, // this plugin uses properties to override the configuration location by default - - gce.checkstyleDirectory.set(project.provider { def directory = project.hasProperty(CHECKSTYLE_DIR_PROPERTY) ? project.rootProject.layout.projectDirectory.dir(project.property(CHECKSTYLE_DIR_PROPERTY) as String) : diff --git a/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsFormatPlugin.groovy b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsFormatPlugin.groovy new file mode 100644 index 00000000000..6b2d8de789a --- /dev/null +++ b/build-logic/plugins/src/main/groovy/org/apache/grails/buildsrc/GrailsFormatPlugin.groovy @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.grails.buildsrc + +import groovy.transform.CompileStatic +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.Copy +import org.gradle.process.ExecSpec +import org.gradle.process.ExecOperations +import javax.inject.Inject +import org.apache.tools.ant.taskdefs.condition.Os + +@CompileStatic +class GrailsFormatPlugin implements Plugin { + + @Override + void apply(Project project) { + registerGitHooks(project) + registerFormattingTasks(project) + } + + private static void registerGitHooks(Project project) { + if (project == project.rootProject) { + project.tasks.register('installGitHooks', Copy) { + it.group = 'verification' + it.description = 'Installs the git pre-commit hook for automatic code formatting' + it.from(project.rootProject.layout.projectDirectory.file('etc/hooks/pre-commit')) + it.into(project.rootProject.layout.projectDirectory.dir('.git/hooks')) + it.fileMode = 0755 + } + } + } + + private static void registerFormattingTasks(Project project) { + ExecOperationsSupport execSupport = project.objects.newInstance(ExecOperationsSupport) + def ideaExecProvider = project.providers.gradleProperty('idea.exec') + .orElse(Os.isFamily(Os.FAMILY_WINDOWS) ? 'format.bat' : 'idea') + def formatFilesProvider = project.providers.gradleProperty('formatFiles') + def rootProjectDir = project.rootProject.projectDir + def projectDir = project.projectDir + + project.tasks.register('formatCode') { task -> + task.group = 'verification' + task.description = 'Formats Java and Groovy source files using the IntelliJ command line formatter' + + task.doLast { + String ideaExec = ideaExecProvider.get() + def filesToFormat = formatFilesProvider.getOrNull() + def settingsFile = new File(rootProjectDir, '.idea/codeStyles/Project.xml') + + if (!settingsFile.exists()) { + throw new RuntimeException("IntelliJ code style settings not found at ${settingsFile.absolutePath}") + } + + try { + execSupport.execOperations.exec { ExecSpec exec -> + exec.commandLine ideaExec + if (ideaExec == 'idea') { + exec.args 'format' + } + exec.args '-s', settingsFile.absolutePath + exec.args '-mask', '*.java,*.groovy' + exec.args '-r' + if (filesToFormat) { + exec.args((filesToFormat.toString()).split(',')) + } else { + exec.args projectDir.absolutePath + } + } + } catch (Exception e) { + task.logger.error("IntelliJ formatter failed to execute.") + task.logger.error("Please ensure IntelliJ command line tools are installed and available on your PATH.") + task.logger.error("See: https://www.jetbrains.com/help/idea/working-with-the-ide-features-from-command-line.html") + throw new RuntimeException("IntelliJ formatter failed. See logs for details.", e) + } + } + } + } +} + +interface ExecOperationsSupport { + @Inject + ExecOperations getExecOperations() +} diff --git a/build.gradle b/build.gradle index eb7b9989196..0d2210597d3 100644 --- a/build.gradle +++ b/build.gradle @@ -15,6 +15,10 @@ * limitations under the License. */ +plugins { + id 'org.apache.grails.gradle.grails-format' +} + import java.time.Instant import java.time.LocalDate import java.time.ZoneOffset diff --git a/etc/hooks/pre-commit b/etc/hooks/pre-commit new file mode 100755 index 00000000000..296e6cc79eb --- /dev/null +++ b/etc/hooks/pre-commit @@ -0,0 +1,41 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e + +# Get staged files that are Groovy or Java +STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.(groovy|java)$' || true) + +if [ -n "$STAGED_FILES" ]; then + echo "Formatting staged Groovy/Java files using IntelliJ formatter..." + + # Convert newline-separated list to comma-separated for Gradle property + FILES_COMMAS=$(echo "$STAGED_FILES" | tr '\n' ',' | sed 's/,$//') + + # Run the Gradle formatting task + ./gradlew :formatCode -PformatFiles="$FILES_COMMAS" + + # Re-stage the files in case they were modified by the formatter + for FILE in $STAGED_FILES; do + if [ -f "$FILE" ]; then + git add "$FILE" + fi + done +fi