diff --git a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java index 6ddba96106a..a1ba77373c1 100644 --- a/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java +++ b/spring-boot-project/spring-boot-devtools/src/main/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessor.java @@ -23,8 +23,11 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.function.Function; +import java.util.function.Supplier; import org.springframework.boot.SpringApplication; import org.springframework.boot.devtools.system.DevToolsEnablementDeducer; @@ -59,6 +62,10 @@ public class DevToolsHomePropertiesPostProcessor implements EnvironmentPostProce private static final Set PROPERTY_SOURCE_LOADERS; + private final Properties systemProperties; + + private final Map environmentVariables; + static { Set propertySourceLoaders = new HashSet<>(); propertySourceLoaders.add(new PropertiesPropertySourceLoader()); @@ -68,6 +75,15 @@ public class DevToolsHomePropertiesPostProcessor implements EnvironmentPostProce PROPERTY_SOURCE_LOADERS = Collections.unmodifiableSet(propertySourceLoaders); } + public DevToolsHomePropertiesPostProcessor() { + this(System.getenv(), System.getProperties()); + } + + DevToolsHomePropertiesPostProcessor(Map environmentVariables, Properties systemProperties) { + this.environmentVariables = environmentVariables; + this.systemProperties = systemProperties; + } + @Override public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { if (DevToolsEnablementDeducer.shouldEnable(Thread.currentThread())) { @@ -122,9 +138,18 @@ public class DevToolsHomePropertiesPostProcessor implements EnvironmentPostProce } protected File getHomeDirectory() { - String home = System.getProperty("user.home"); - if (StringUtils.hasLength(home)) { - return new File(home); + return getHomeDirectory(() -> this.environmentVariables.get("SPRING_DEVTOOLS_HOME"), + () -> this.systemProperties.getProperty("spring.devtools.home"), + () -> this.systemProperties.getProperty("user.home")); + } + + @SafeVarargs + private final File getHomeDirectory(Supplier... pathSuppliers) { + for (Supplier pathSupplier : pathSuppliers) { + String path = pathSupplier.get(); + if (StringUtils.hasText(path)) { + return new File(path); + } } return null; } diff --git a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java index 441d76835b4..8505cb842dc 100644 --- a/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java +++ b/spring-boot-project/spring-boot-devtools/src/test/java/org/springframework/boot/devtools/env/DevToolsHomePropertiesPostProcessorTests.java @@ -21,6 +21,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; +import java.util.Collections; +import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.BeforeEach; @@ -47,9 +49,12 @@ class DevToolsHomePropertiesPostProcessorTests { private File home; + private File customHome; + @BeforeEach void setup(@TempDir File tempDir) { - this.home = tempDir; + this.home = new File(tempDir, "default-home"); + this.customHome = new File(tempDir, "custom-home"); this.configDir = this.home + "/.config/spring-boot/"; new File(this.configDir).mkdirs(); } @@ -63,6 +68,29 @@ class DevToolsHomePropertiesPostProcessorTests { assertThat(environment.getProperty("abc")).isEqualTo("def"); } + @Test + void loadsPropertiesFromCustomHomeDirectorySetUsingSystemProperty() throws Exception { + Properties properties = new Properties(); + properties.put("uvw", "xyz"); + writeFile(properties, this.customHome, ".config/spring-boot/spring-boot-devtools.properties"); + Properties systemProperties = new Properties(); + systemProperties.setProperty("spring.devtools.home", this.customHome.getAbsolutePath()); + ConfigurableEnvironment environment = getPostProcessedEnvironment(systemProperties); + assertThat(environment.getProperty("uvw")).isEqualTo("xyz"); + } + + @Test + void loadsPropertiesFromCustomHomeDirectorySetUsingEnvironmentVariable() throws Exception { + Properties properties = new Properties(); + properties.put("uvw", "xyz"); + writeFile(properties, this.customHome, ".config/spring-boot/spring-boot-devtools.properties"); + Properties systemProperties = new Properties(); + systemProperties.setProperty("spring.devtools.home", this.customHome.getAbsolutePath()); + ConfigurableEnvironment environment = getPostProcessedEnvironment( + Collections.singletonMap("SPRING_DEVTOOLS_HOME", this.customHome.getAbsolutePath())); + assertThat(environment.getProperty("uvw")).isEqualTo("xyz"); + } + @Test void loadsPropertiesFromConfigDirectoryUsingProperties() throws Exception { Properties properties = new Properties(); @@ -151,15 +179,39 @@ class DevToolsHomePropertiesPostProcessorTests { assertThat(environment.getProperty("abc")).isNull(); } - private void writeFile(Properties properties, String s) throws IOException { - OutputStream out = new FileOutputStream(new File(this.home, s)); - properties.store(out, null); - out.close(); + private void writeFile(Properties properties, String path) throws IOException { + writeFile(properties, this.home, path); + } + + private void writeFile(Properties properties, File home, String path) throws IOException { + File file = new File(home, path); + file.getParentFile().mkdirs(); + try (OutputStream out = new FileOutputStream(file)) { + properties.store(out, null); + } } private ConfigurableEnvironment getPostProcessedEnvironment() throws Exception { + return getPostProcessedEnvironment(null, null); + } + + private ConfigurableEnvironment getPostProcessedEnvironment(Properties systemProperties) throws Exception { + return getPostProcessedEnvironment(null, systemProperties); + } + + private ConfigurableEnvironment getPostProcessedEnvironment(Map env) throws Exception { + return getPostProcessedEnvironment(env, null); + } + + private ConfigurableEnvironment getPostProcessedEnvironment(Map env, Properties systemProperties) + throws Exception { + if (systemProperties == null) { + systemProperties = new Properties(); + systemProperties.setProperty("user.home", this.home.getAbsolutePath()); + } ConfigurableEnvironment environment = new MockEnvironment(); - MockDevToolHomePropertiesPostProcessor postProcessor = new MockDevToolHomePropertiesPostProcessor(); + DevToolsHomePropertiesPostProcessor postProcessor = new DevToolsHomePropertiesPostProcessor( + (env != null) ? env : Collections.emptyMap(), systemProperties); runPostProcessor(() -> postProcessor.postProcessEnvironment(environment, null)); return environment; } @@ -170,13 +222,4 @@ class DevToolsHomePropertiesPostProcessorTests { thread.join(); } - private class MockDevToolHomePropertiesPostProcessor extends DevToolsHomePropertiesPostProcessor { - - @Override - protected File getHomeDirectory() { - return DevToolsHomePropertiesPostProcessorTests.this.home; - } - - } - } diff --git a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/devtools.adoc b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/devtools.adoc index 2e534f01526..fedd92b08fa 100644 --- a/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/devtools.adoc +++ b/spring-boot-project/spring-boot-docs/src/docs/asciidoc/using/devtools.adoc @@ -285,6 +285,9 @@ For example, to configure restart to always use a <