diff --git a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java index 743582f056a7d..c935c2ba45bf4 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/steps/ConfigGenerationBuildStep.java @@ -40,6 +40,7 @@ import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.ConfigClassBuildItem; import io.quarkus.deployment.builditem.ConfigMappingBuildItem; @@ -69,6 +70,7 @@ import io.quarkus.gizmo.MethodCreator; import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; +import io.quarkus.paths.PathCollection; import io.quarkus.runtime.LaunchMode; import io.quarkus.runtime.annotations.StaticInitSafe; import io.quarkus.runtime.configuration.ConfigBuilder; @@ -267,8 +269,11 @@ void generateConfigClass( List runTimeConfigBuilders) throws IOException { - reportUnknownBuildProperties(launchModeBuildItem.getLaunchMode(), - configItem.getReadResult().getUnknownBuildProperties()); + // So it only reports during the build, because it is very likely that the property is available in runtime + // and, it will be caught by the RuntimeConfig and log double warnings + if (!launchModeBuildItem.getLaunchMode().isDevOrTest()) { + ConfigDiagnostic.unknownProperties(configItem.getReadResult().getUnknownBuildProperties()); + } if (liveReloadBuildItem.isLiveReload()) { return; @@ -320,14 +325,6 @@ void generateConfigClass( .run(); } - private static void reportUnknownBuildProperties(LaunchMode launchMode, Set unknownBuildProperties) { - // So it only reports during the build, because it is very likely that the property is available in runtime - // and, it will be caught by the RuntimeConfig and log double warnings - if (!launchMode.isDevOrTest()) { - ConfigDiagnostic.unknownProperties(unknownBuildProperties); - } - } - @BuildStep public void suppressNonRuntimeConfigChanged( BuildProducer suppressNonRuntimeConfigChanged) { @@ -441,6 +438,31 @@ public void watchConfigFiles(BuildProducer wa } } + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void unknownConfigFiles( + ApplicationArchivesBuildItem applicationArchives, + LaunchModeBuildItem launchModeBuildItem, + ConfigRecorder configRecorder) throws Exception { + + PathCollection rootDirectories = applicationArchives.getRootArchive().getRootDirectories(); + if (!rootDirectories.isSinglePath()) { + return; + } + + Set buildTimeFiles = new HashSet<>(); + buildTimeFiles.addAll(ConfigDiagnostic.configFiles(rootDirectories.getSinglePath())); + buildTimeFiles.addAll(ConfigDiagnostic.configFilesFromLocations()); + + // Report always at build time since config folder and locations may differ from build to runtime + ConfigDiagnostic.unknownConfigFiles(buildTimeFiles); + + // No need to include the application files, because they don't change + if (!launchModeBuildItem.getLaunchMode().isDevOrTest()) { + configRecorder.unknownConfigFiles(); + } + } + @BuildStep(onlyIf = NativeOrNativeSourcesBuild.class) @Record(ExecutionTime.RUNTIME_INIT) void warnDifferentProfileUsedBetweenBuildAndRunTime(ConfigRecorder configRecorder) { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java index b197c7d91814c..6a28d740a1713 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigDiagnostic.java @@ -1,17 +1,30 @@ package io.quarkus.runtime.configuration; +import static io.smallrye.config.SmallRyeConfig.SMALLRYE_CONFIG_LOCATIONS; + +import java.io.IOException; +import java.net.URI; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.NoSuchElementException; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Consumer; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.ConfigProvider; +import org.eclipse.microprofile.config.spi.ConfigSource; import org.jboss.logging.Logger; import io.quarkus.runtime.ImageMode; +import io.smallrye.config.SmallRyeConfig; import io.smallrye.config.common.utils.StringUtil; /** @@ -152,4 +165,79 @@ public static String getNiceErrorMessage() { public static Set getErrorKeys() { return new HashSet<>(errorKeys); } + + private static final DirectoryStream.Filter CONFIG_FILES_FILTER = new DirectoryStream.Filter<>() { + @Override + public boolean accept(final Path entry) { + // Ignore .properties, because we know these are have a default loader in core + // Ignore profile files. The loading rules require the main file to be present, so we only need the type + String filename = entry.getFileName().toString(); + return Files.isRegularFile(entry) && filename.startsWith("application.") && !filename.endsWith(".properties"); + } + }; + + public static Set configFiles(Path configFilesLocation) throws IOException { + if (!Files.exists(configFilesLocation)) { + return Collections.emptySet(); + } + + Set configFiles = new HashSet<>(); + try (DirectoryStream candidates = Files.newDirectoryStream(configFilesLocation, CONFIG_FILES_FILTER)) { + for (Path candidate : candidates) { + configFiles.add(candidate.toString()); + } + } + return configFiles; + } + + public static Set configFilesFromLocations() throws Exception { + SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class); + + Set configFiles = new HashSet<>(); + configFiles.addAll(configFiles(Paths.get(System.getProperty("user.dir"), "config"))); + Optional> optionalLocations = config.getOptionalValues(SMALLRYE_CONFIG_LOCATIONS, URI.class); + optionalLocations.ifPresent(new Consumer>() { + @Override + public void accept(final List locations) { + for (URI location : locations) { + Path path = location.getScheme() != null && location.getScheme().equals("file") ? Paths.get(location) + : Paths.get(location.getPath()); + if (Files.isDirectory(path)) { + try { + configFiles.addAll(configFiles(path)); + } catch (IOException e) { + // Ignore + } + } + } + } + }); + + return configFiles; + } + + public static void unknownConfigFiles(final Set configFiles) { + SmallRyeConfig config = ConfigProvider.getConfig().unwrap(SmallRyeConfig.class); + Set configNames = new HashSet<>(); + for (ConfigSource configSource : config.getConfigSources()) { + if (configSource.getName() != null && configSource.getName().contains("application")) { + configNames.add(configSource.getName()); + } + } + + for (String configFile : configFiles) { + boolean found = false; + for (String configName : configNames) { + if (configName.contains(configFile)) { + found = true; + break; + } + } + if (!found) { + log.warnf( + "Unrecognized configuration file %s found; Please, check if your are providing the proper extension to load the file", + configFile); + } + } + } } diff --git a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java index 2e42d598f7e95..fd04bf02a8817 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/configuration/ConfigRecorder.java @@ -97,4 +97,8 @@ public void handleNativeProfileChange(List buildProfiles) { } } } + + public void unknownConfigFiles() throws Exception { + ConfigDiagnostic.unknownConfigFiles(ConfigDiagnostic.configFilesFromLocations()); + } } diff --git a/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/UnknownConfigFilesTest.java b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/UnknownConfigFilesTest.java new file mode 100644 index 0000000000000..1537939af1b1e --- /dev/null +++ b/integration-tests/test-extension/extension/deployment/src/test/java/io/quarkus/config/UnknownConfigFilesTest.java @@ -0,0 +1,38 @@ +package io.quarkus.config; + +import static io.smallrye.common.constraint.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.stream.Collectors; + +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +class UnknownConfigFilesTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addAsResource(EmptyAsset.INSTANCE, "application.properties") + .addAsResource(EmptyAsset.INSTANCE, "application-prod.properties") + .addAsResource(EmptyAsset.INSTANCE, "application.yaml")) + .setLogRecordPredicate(record -> record.getLevel().intValue() >= Level.WARNING.intValue()) + .assertLogRecords(logRecords -> { + List unknownConfigFiles = logRecords.stream() + .filter(l -> l.getMessage().startsWith("Unrecognized configuration file")) + .collect(Collectors.toList()); + + assertEquals(1, unknownConfigFiles.size()); + assertTrue(unknownConfigFiles.get(0).getParameters()[0].toString().contains("application.yaml")); + }); + + @Test + void unknownConfigFiles() { + + } +}