Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import java.util.jar.Manifest

class JmixPlugin implements Plugin<Project> {

public static final String DEFAULT_CONF_DIR = '.jmix/conf'

public static final String PROVIDED_RUNTIME_CONFIGURATION_NAME = 'providedRuntime'
public static final String PRODUCTION_RUNTIME_CLASSPATH_CONFIGURATION_NAME = 'productionRuntimeClasspath'

Expand Down Expand Up @@ -314,7 +316,7 @@ class JmixPlugin implements Plugin<Project> {
def confDir = resolveConfDir(project, mainProperties)

project.logger.lifecycle("Delete directory: {}", confDir)
delete "${confDir}"
delete confDir
} else {
project.logger.lifecycle("Resource directory not found")
return
Expand Down Expand Up @@ -396,26 +398,47 @@ class JmixPlugin implements Plugin<Project> {
}
}

private static String resolveConfDir(Project project, Properties mainProperties) {
/**
* Resolves the directory used by the application as 'jmix.core.conf-dir' at runtime.
*
* The runtime resolves this property against its working directory, which is the project directory
* both for the Gradle 'bootRun' task and for Studio run configurations. So relative paths and the
* '${user.dir}' placeholder are resolved against the project directory, not the build root directory:
* these are different directories when the application is a subproject with a custom 'projectDir'.
*/
private static File resolveConfDir(Project project, Properties mainProperties) {
def profilesList = resolveActiveProfiles(project, mainProperties)

def confDir = null
if (!profilesList.isEmpty()) {
for (def profileName : profilesList) {
project.logger.lifecycle("Check profile: {}", profileName)
def profileProperties = loadProperties(project, profileName)
confDir = profileProperties.getProperty("jmix.core.conf-dir") ?: profileProperties.getProperty("jmix.core.confDir") ?: null
confDir = getConfDirProperty(profileProperties)
if (confDir != null) {
break
}
}
}

if (confDir == null) {
confDir = mainProperties.getProperty("jmix.core.conf-dir") ?: mainProperties.getProperty("jmix.core.confDir") ?: "${project.rootDir}/.jmix/conf"
confDir = getConfDirProperty(mainProperties) ?: DEFAULT_CONF_DIR
}

return confDir
return project.file(expandPathPlaceholders(project, confDir))
}

private static String getConfDirProperty(Properties properties) {
return properties.getProperty("jmix.core.conf-dir") ?: properties.getProperty("jmix.core.confDir") ?: null
}

/**
* Replaces the placeholders that the running application resolves in path properties.
* '${user.dir}' becomes the project directory because that is the working directory of the application.
*/
private static String expandPathPlaceholders(Project project, String path) {
return path.replace('${user.dir}', project.projectDir.absolutePath)
.replace('${user.home}', System.getProperty('user.home'))
}

private static List<String> resolveActiveProfiles(Project project, Properties mainProperties) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Copyright 2026 Haulmont.
*
* Licensed 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 io.jmix.gradle

import org.gradle.testkit.runner.GradleRunner
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir

import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption

import static org.junit.jupiter.api.Assertions.assertFalse
import static org.junit.jupiter.api.Assertions.assertTrue

/**
* The 'cleanConf' task must delete the directory that the application actually uses at runtime.
* The runtime resolves 'jmix.core.conf-dir' against its working directory, which for 'bootRun'
* is the application module dir - not the build root dir.
*/
class CleanConfFunctionalTest {

private static final String APP_MODULE_PATH = 'modules/backend/app'

@TempDir
Path testProjectDir

@Test
@Tag('slowTests')
void defaultConfDirIsResolvedAgainstApplicationModule() {
copyFixture('nonstandard-layout-conf-dir', testProjectDir)

Path rootConfDir = createConfDir('.jmix/conf')
Path appConfDir = createAppConfDir('.jmix/conf')

runCleanConf()

assertFalse(Files.exists(appConfDir), "conf dir of the application module must be deleted: ${appConfDir}")
assertTrue(Files.exists(rootConfDir), "conf dir of the build root must be left alone: ${rootConfDir}")
}

@Test
@Tag('slowTests')
void relativeConfDirPropertyIsResolvedAgainstApplicationModule() {
copyFixture('nonstandard-layout-conf-dir', testProjectDir)
appendAppProperty('jmix.core.conf-dir = custom-conf')

Path rootConfDir = createConfDir('custom-conf')
Path appConfDir = createAppConfDir('custom-conf')

runCleanConf()

assertFalse(Files.exists(appConfDir), "conf dir of the application module must be deleted: ${appConfDir}")
assertTrue(Files.exists(rootConfDir), "conf dir of the build root must be left alone: ${rootConfDir}")
}

@Test
@Tag('slowTests')
void userDirPlaceholderInConfDirPropertyIsExpanded() {
copyFixture('nonstandard-layout-conf-dir', testProjectDir)
appendAppProperty('jmix.core.conf-dir = ${user.dir}/.jmix/conf')

Path rootConfDir = createConfDir('.jmix/conf')
Path appConfDir = createAppConfDir('.jmix/conf')

runCleanConf()

assertFalse(Files.exists(appConfDir), "conf dir of the application module must be deleted: ${appConfDir}")
assertTrue(Files.exists(rootConfDir), "conf dir of the build root must be left alone: ${rootConfDir}")
}

@Test
@Tag('slowTests')
void absoluteConfDirPropertyIsUsedAsIs() {
copyFixture('nonstandard-layout-conf-dir', testProjectDir)

Path absoluteConfDir = Files.createDirectories(testProjectDir.resolve('outside/conf'))
Files.writeString(absoluteConfDir.resolve('marker.txt'), 'marker')
appendAppProperty("jmix.core.conf-dir = ${absoluteConfDir.toString().replace('\\', '/')}")

Path appConfDir = createAppConfDir('.jmix/conf')

runCleanConf()

assertFalse(Files.exists(absoluteConfDir), "conf dir given as an absolute path must be deleted: ${absoluteConfDir}")
assertTrue(Files.exists(appConfDir), "default conf dir must be left alone when the property is set: ${appConfDir}")
}

private void runCleanConf() {
GradleRunner.create()
.withProjectDir(testProjectDir.toFile())
.withArguments(':app:cleanConf', '--stacktrace')
.withPluginClasspath()
.forwardOutput()
.build()
}

private Path createConfDir(String relativePath) {
return createMarkedDir(testProjectDir.resolve(relativePath))
}

private Path createAppConfDir(String relativePath) {
return createMarkedDir(testProjectDir.resolve(APP_MODULE_PATH).resolve(relativePath))
}

private static Path createMarkedDir(Path dir) {
Files.createDirectories(dir)
Files.writeString(dir.resolve('marker.txt'), 'marker')
return dir
}

private void appendAppProperty(String line) {
Path propertiesFile = testProjectDir.resolve("${APP_MODULE_PATH}/src/main/resources/application.properties")
Files.writeString(propertiesFile, "${Files.readString(propertiesFile)}\n${line}\n")
}

private static void copyFixture(String name, Path target) {
Path source = Path.of(CleanConfFunctionalTest.getResource("/fixtures/${name}").toURI())

Files.walk(source).forEach { sourcePath ->
Path targetPath = target.resolve(source.relativize(sourcePath).toString())
if (Files.isDirectory(sourcePath)) {
Files.createDirectories(targetPath)
} else {
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
plugins {
id 'io.jmix'
id 'java'
}

jmix {
useBom = false
entitiesEnhancing {
enabled = false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
spring.application.name = app
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// The Jmix application is a subproject of the root build with a custom projectDir,
// so the application module dir differs from the build root dir.
rootProject.name = 'jmix-gradle-plugin-conf-dir'

include ':app'
project(':app').projectDir = file('modules/backend/app')