gnodet-bot commented on code in PR #11405:
URL: https://github.com/apache/maven/pull/11405#discussion_r4256304613
##########
api/maven-api-core/src/main/java/org/apache/maven/api/Constants.java:
##########
@@ -868,5 +868,17 @@ public final class Constants {
@Config(type = "java.lang.Boolean", defaultValue = "false")
public static final String MAVEN_MODEL_DEPENDENCY_INTERPOLATION_FULL =
"maven.model.dependencyInterpolation.full";
+ /**
+ * System property to keep temp material for diagnostics.
+ * When set to {@code true} via {@code -Dmaven.tempfile.keep=true},
temporary files and directories
+ * created by {@link org.apache.maven.api.services.TempFileService} are
not deleted at session end.
+ * This is a JVM system property only (read via {@link
Boolean#getBoolean(String)});
+ * it is not resolved through Maven's property mechanism (pom.xml,
profiles, settings.xml).
+ *
+ * @since 4.1.0
+ */
+ @Config(type = "java.lang.Boolean", defaultValue = "false")
Review Comment:
⚠️ **`@Config` without `source = Config.Source.SYSTEM_PROPERTIES`
contradicts the Javadoc**
The Javadoc states this property is "not resolved through Maven's property
mechanism (pom.xml, profiles, settings.xml)" — but `@Config` with the default
`source = USER_PROPERTIES` signals to Maven's config tooling that users CAN set
it through those mechanisms. In practice, anything set via
`pom.xml`/`settings.xml` goes through Maven's property merger (not
`System.setProperty()`), so `Boolean.getBoolean()` silently ignores it.
Choose one:
1. **Make it truly Maven-property-configurable** by replacing
`Boolean.getBoolean(MAVEN_TEMPFILE_KEEP)` with session property resolution
(e.g.
`Boolean.parseBoolean(session.getUserProperties().get(MAVEN_TEMPFILE_KEEP))`) —
and update the Javadoc accordingly.
2. **Keep it a JVM-only `-D` flag** by removing `@Config` (since it's not
participting in Maven's config resolution) or adding `source =
Config.Source.SYSTEM_PROPERTIES` + `readOnly = false` if a SYSTEM_PROPERTIES
source without readOnly is appropriate here.
```suggestion
@Config(type = "java.lang.Boolean", defaultValue = "false", source =
Config.Source.USER_PROPERTIES)
public static final String MAVEN_TEMPFILE_KEEP = "maven.tempfile.keep";
```
Note: the suggestion above keeps the annotation but makes the source
explicit — the real fix is aligning the implementation with the declared source.
##########
api/maven-api-core/src/main/java/org/apache/maven/api/services/TempFileService.java:
##########
@@ -0,0 +1,75 @@
+/*
+ * 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
+ *
+ * http://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.maven.api.services;
+
+import java.io.IOException;
+import java.nio.file.Path;
+
+import org.apache.maven.api.Service;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.annotations.Experimental;
+import org.apache.maven.api.annotations.Nonnull;
+import org.apache.maven.api.annotations.Nullable;
+
+/**
+ * Service to create and track temporary files/directories for a Maven build.
+ * All created paths are deleted automatically when the session ends.
+ *
+ * @since 4.1.0
+ */
+@Experimental
+public interface TempFileService extends Service {
+
+ /**
+ * Creates a temp file in the default temp directory.
+ */
Review Comment:
📝 **Missing `@throws` on all four factory methods and `cleanup()`**
Public API methods that declare `throws IOException` must document it.
Without `@throws`, callers have no Javadoc guidance on when the exception is
thrown. Same applies to the other three `create*` methods and `cleanup()`.
Example fix for all five:
```suggestion
/**
* Creates a temp file in the default temp directory.
*
* @throws IOException if the file cannot be created
*/
```
##########
impl/maven-core/src/main/java/org/apache/maven/internal/impl/TempFileCleanupParticipant.java:
##########
@@ -0,0 +1,59 @@
+/*
+ * 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
+ *
+ * http://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.maven.internal.impl;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.apache.maven.AbstractMavenLifecycleParticipant;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.services.TempFileService;
+import org.apache.maven.execution.MavenSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Hooks into the Maven lifecycle and removes all temp material after the
session.
+ */
+@Named
+@Singleton
+public final class TempFileCleanupParticipant extends
AbstractMavenLifecycleParticipant {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(TempFileCleanupParticipant.class);
+
+ private final TempFileService tempFileService;
+
+ @Inject
+ public TempFileCleanupParticipant(final TempFileService tempFileService) {
+ this.tempFileService = tempFileService;
+ }
+
+ @Override
+ public void afterSessionEnd(final MavenSession mavenSession) {
+ // Bridge to the API Session (available in Maven 4).
+ final Session apiSession = mavenSession.getSession();
+ try {
+ tempFileService.cleanup(apiSession);
+ } catch (final Exception e) {
+ // We're at session end; just log. Maven already reported build
result.
+ LOGGER.warn("Temp cleanup failed: {}", e.getMessage());
Review Comment:
🐛 **Stack trace lost; catch is too broad**
Two problems:
1. `e.getMessage()` logs only the exception message, discarding the full
stack trace. In production, when temp cleanup fails silently at session end,
the message alone ("Temp cleanup failed: null" for NPE, or a terse IOException
message) makes root-cause diagnosis impossible. SLF4J supports logging the full
exception: `LOGGER.warn("Temp cleanup failed", e)` (no `{}` placeholder — SLF4J
appends the throwable automatically).
2. `cleanup()` only declares `throws IOException`. Catching `Exception`
broadly hides any unexpected `RuntimeException` (NPE, `IllegalStateException`)
under the same generic log message.
```suggestion
} catch (final IOException e) {
// We're at session end; just log. Maven already reported build
result.
LOGGER.warn("Temp cleanup failed", e);
}
```
##########
impl/maven-core/src/test/java/org/apache/maven/internal/impl/DefaultTempFileServiceTest.java:
##########
@@ -0,0 +1,237 @@
+/*
+ * 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
+ *
+ * http://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.maven.internal.impl;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+
+import org.apache.maven.api.Constants;
+import org.apache.maven.api.Session;
+import org.apache.maven.api.SessionData;
+import org.apache.maven.api.services.TempFileService;
+import org.apache.maven.execution.MavenSession;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests for DefaultTempFileService and TempFileCleanupParticipant.
+ */
+class DefaultTempFileServiceTest {
+
+ @AfterEach
+ void clearKeepProp() {
+ System.clearProperty(Constants.MAVEN_TEMPFILE_KEEP);
+ }
+
+ @Test
+ void createsFilesAndDirectoriesAndCleansThemUp() throws IOException {
+ final TempFileService svc = new DefaultTempFileService();
+ final SessionData data = new MapBackedSessionData();
+ final Session session = mock(Session.class);
+ when(session.getData()).thenReturn(data);
+
+ final Path f1 = svc.createTempFile(session, "tfs-", ".bin");
+ final Path d1 = svc.createTempDirectory(session, "tfs-");
+ final Path nested = Files.createTempFile(d1, "inner-", ".tmp");
+
+ assertTrue(Files.exists(f1), "temp file must exist");
+ assertTrue(Files.exists(d1), "temp dir must exist");
+ assertTrue(Files.exists(nested), "nested file must exist");
+
+ svc.cleanup(session);
+
+ assertFalse(Files.exists(f1), "temp file must be deleted");
+ assertFalse(Files.exists(d1), "temp dir tree must be deleted");
+ assertFalse(Files.exists(nested), "nested file must be deleted");
+ }
+
+ @Test
+ void registerExternalPathIsAlsoDeleted() throws IOException {
+ final TempFileService svc = new DefaultTempFileService();
+ final SessionData data = new MapBackedSessionData();
+ final Session session = mock(Session.class);
+ when(session.getData()).thenReturn(data);
+
+ final Path externalDir = Files.createTempDirectory("ext-");
+ final Path nested = Files.createTempFile(externalDir, "ext-inner-",
".tmp");
+ assertTrue(Files.exists(externalDir));
+ assertTrue(Files.exists(nested));
+
+ svc.register(session, externalDir);
+ svc.cleanup(session);
+
+ assertFalse(Files.exists(externalDir), "registered external dir must
be deleted recursively");
+ assertFalse(Files.exists(nested));
+ }
+
+ @Test
+ void createsFileInSpecifiedDirectory() throws IOException {
+ final TempFileService svc = new DefaultTempFileService();
+ final SessionData data = new MapBackedSessionData();
+ final Session session = mock(Session.class);
+ when(session.getData()).thenReturn(data);
+
+ final Path customDir = Files.createTempDirectory("custom-dir-");
+ try {
+ final Path f = svc.createTempFile(session, "dir-", ".tmp",
customDir);
+ assertTrue(Files.exists(f), "temp file must exist");
+ assertTrue(f.startsWith(customDir), "temp file must be inside the
specified directory");
+
+ svc.cleanup(session);
+
+ assertFalse(Files.exists(f), "temp file must be deleted");
+ } finally {
+ Files.deleteIfExists(customDir);
+ }
+ }
+
+ @Test
+ void createsDirectoryInSpecifiedParent() throws IOException {
+ final TempFileService svc = new DefaultTempFileService();
+ final SessionData data = new MapBackedSessionData();
+ final Session session = mock(Session.class);
+ when(session.getData()).thenReturn(data);
+
+ final Path parentDir = Files.createTempDirectory("parent-dir-");
+ try {
+ final Path d = svc.createTempDirectory(session, "nested-",
parentDir);
+ assertTrue(Files.exists(d), "temp directory must exist");
+ assertTrue(d.startsWith(parentDir), "temp directory must be inside
the specified parent");
+
+ svc.cleanup(session);
+
+ assertFalse(Files.exists(d), "temp directory must be deleted");
+ } finally {
+ Files.deleteIfExists(parentDir);
+ }
+ }
+
+ @Test
+ void keepPropertySkipsCleanup() throws IOException {
+ final TempFileService svc = new DefaultTempFileService();
+ final SessionData data = new MapBackedSessionData();
+ final Session session = mock(Session.class);
+ when(session.getData()).thenReturn(data);
+
+ final Path f = svc.createTempFile(session, "keep-", ".tmp");
+ assertTrue(Files.exists(f));
+
+ System.setProperty(Constants.MAVEN_TEMPFILE_KEEP, "true");
+ svc.cleanup(session);
+
+ assertTrue(Files.exists(f), "cleanup must be skipped when
-Dmaven.tempfile.keep=true");
+
+ // turn cleanup back on and verify it deletes
+ System.clearProperty(Constants.MAVEN_TEMPFILE_KEEP);
+ svc.cleanup(session);
+ assertFalse(Files.exists(f));
+ }
+
+ @Test
+ void cleanupParticipantDelegatesCleanupToService() throws IOException {
+ final TempFileService svc = new DefaultTempFileService();
+ final SessionData data = new MapBackedSessionData();
+ final Session apiSession = mock(Session.class);
+ when(apiSession.getData()).thenReturn(data);
+
+ final Path f = svc.createTempFile(apiSession, "participant-", ".tmp");
Review Comment:
⚠️ **`System.setProperty` without local try-finally — parallel execution
risk**
`@AfterEach` mitigates sequential pollution but does not protect against
parallel test execution (JUnit 5 allows it). If `svc.cleanup(session)` on line
164 throws, `@AfterEach` still runs — but if another test starts between the
`setProperty` and the `@AfterEach` cleanup, it will see the mutated property.
Guard with a `try-finally` inside the test method:
```suggestion
System.setProperty(Constants.MAVEN_TEMPFILE_KEEP, "true");
try {
svc.cleanup(session);
assertTrue(Files.exists(f), "cleanup must be skipped when
-Dmaven.tempfile.keep=true");
} finally {
System.clearProperty(Constants.MAVEN_TEMPFILE_KEEP);
}
```
--
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]