codeconsole commented on code in PR #16472: URL: https://github.com/apache/grails-core/pull/16472#discussion_r4170449962
########## grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsProjectOutputDirProvider.groovy: ########## @@ -0,0 +1,132 @@ +/* + * 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.grails.gradle.plugin.core + +import java.nio.file.Path + +import groovy.transform.CompileStatic + +import org.gradle.api.file.Directory +import org.gradle.api.logging.Logger +import org.gradle.api.logging.Logging +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Internal +import org.gradle.process.CommandLineArgumentProvider + +import grails.util.BuildSettings + +/** + * Provides a system property naming one of the build's directories, relative to the project directory, to forked + * JVM tasks: {@code grails.project.class.dir} for the main source set's Groovy classes + * ({@link BuildSettings#PROJECT_CLASSES_DIR}), {@code grails.project.resource.dir} for its resources + * ({@link BuildSettings#PROJECT_RESOURCES_DIR}), and {@code grails.project.target.dir} for the build directory itself + * ({@link BuildSettings#PROJECT_TARGET_DIR}), where development keeps its restart marker ({@code .grailspid}). + * + * <p>In development a changed source file is compiled again into {@link BuildSettings#BUILD_CLASSES_PATH}, and a + * changed message bundle is copied into {@link BuildSettings#BUILD_RESOURCES_PATH}, each resolved against the directory + * of the project that owns the file; {@link BuildSettings#RESOURCES_DIR} is where the application reads resources + * from in development. Without these properties they fall back to {@code build/classes/groovy/main} and + * {@code build/resources/main}, so an application whose build directory is elsewhere (a second instance of one + * checkout built with {@code -PbuildDir}, say) had each change compiled or copied where it does not load from: the + * artefact was reloaded from the class it already had, and the old code went on answering.</p> + * + * <p>The path is relative because it is joined to a project directory, and its names are separated by {@code /} on + * every platform, as the fallbacks are, because {@code IOUtils} compares the classes path with class locations + * written as URLs. When the directory has no path relative to the project, such as one on another drive on Windows, + * no property is passed, the application keeps the fallback, and a warning names both directories, since changes + * will then go where the application does not load from.</p> + * + * <p>A build that sets the property on the task itself ({@code systemProperty 'grails.project.class.dir', ...}) keeps + * its value: nothing is passed then. The task's system properties are read when the task runs, so a value set after + * the plugin is applied counts too.</p> + * + * <p>A directory outside the project gives a path that climbs out of it ({@code ../}). Joining it, as + * {@code GrailsApp.recompile}, {@code MainClassFinder} and the i18n plugin do, resolves correctly; the {@code IOUtils} + * lookups that compare it with a class location find no match and fall back as they did before.</p> + * + * <p>The paths are also joined to the directory of a plugin subproject whose sources development watches, so they + * assume every project lays out its build directory in the same way relative to itself, as Gradle's {@code -PbuildDir} + * or an {@code allprojects} block does. A plugin subproject whose build directory is laid out differently from the + * application's has its changes compiled where it does not load classes from.</p> + * + * <p>All four values are {@link Internal}, as for {@link GrailsAppBaseDirProvider}: the directories are the task's + * classpath, already tracked there, and the task's system properties are its inputs already. The directory is read + * when the task runs, so a build directory changed after the plugin is applied is the one passed.</p> + */ +@CompileStatic +class GrailsProjectOutputDirProvider implements CommandLineArgumentProvider { + + private static final Logger LOG = Logging.getLogger(GrailsProjectOutputDirProvider) + + @Internal + final String systemProperty + + @Internal + final File projectDir + + @Internal + final Provider<Directory> directory + + @Internal + final Map<String, ?> taskSystemProperties + + /** + * @param systemProperty the system property to pass, {@link BuildSettings#PROJECT_CLASSES_DIR}, + * {@link BuildSettings#PROJECT_RESOURCES_DIR} or {@link BuildSettings#PROJECT_TARGET_DIR} + * @param projectDir the project directory, which the path is relative to + * @param directory the output directory the property names + * @param taskSystemProperties the task's own system properties, a value set there is kept + */ + GrailsProjectOutputDirProvider(String systemProperty, File projectDir, Provider<Directory> directory, + Map<String, ?> taskSystemProperties) { + this.systemProperty = systemProperty + this.projectDir = projectDir + this.directory = directory + this.taskSystemProperties = taskSystemProperties + } + + @Override + Iterable<String> asArguments() { + if (taskSystemProperties.containsKey(systemProperty)) { + return [] + } + File dir = directory.get().asFile + String relative = relativePath(projectDir.toPath(), dir.toPath()) + if (relative == null) { + LOG.warn('{} is not passed: {} has no path relative to the project directory {}, so development reloading ' + Review Comment: Thanks, done in 03699f59ab. Each property's warning now says what its own fallback breaks: for `grails.project.class.dir` that a changed class is compiled into `build/classes/groovy/main` and not reloaded; for `grails.project.resource.dir` that resources are read from, and a changed message bundle copied into, `build/resources/main`; and for `grails.project.target.dir` that the restart marker goes to `build/` and an error is logged at each start if there is none. A directory on another drive still logs one line per property, but each says something different, and `GrailsProjectOutputDirProviderSpec` checks the three. The class Javadoc now says only the classes and resources directories are tracked through the classpath; the build directory itself only says where the application writes its restart marker, which nothing the task produces depends on. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
