diff options
Diffstat (limited to 'sonar-scanner-engine/src/main/java')
28 files changed, 507 insertions, 287 deletions
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/JGitCleanupService.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/JGitCleanupService.java new file mode 100644 index 00000000000..28e052cfa4e --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/JGitCleanupService.java @@ -0,0 +1,47 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.bootstrap; + +import java.lang.reflect.Method; +import org.eclipse.jgit.internal.util.CleanupService; + +/** + * Normally, JGit terminates with a shutdown hook. Since we also want to support running the Scanner Engine in the same JVM, this allows triggering shutdown manually. + */ +class JGitCleanupService implements AutoCloseable { + + private final Method shutDownMethod; + private final CleanupService cleanupService; + + public JGitCleanupService() { + cleanupService = new CleanupService(); + try { + shutDownMethod = CleanupService.class.getDeclaredMethod("shutDown"); + } catch (NoSuchMethodException e) { + throw new IllegalStateException("Unable to find method 'shutDown' on JGit CleanupService", e); + } + shutDownMethod.setAccessible(true); + } + + @Override + public void close() throws Exception { + shutDownMethod.invoke(cleanupService); + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java index 0fe2c3ad479..bd8d5b9b99c 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerMain.java @@ -21,17 +21,21 @@ package org.sonar.scanner.bootstrap; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.OutputStreamAppender; import com.google.gson.Gson; import com.google.gson.annotations.SerializedName; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.OutputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import javax.annotation.CheckForNull; +import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; import org.slf4j.LoggerFactory; import org.sonar.api.utils.MessageException; @@ -49,11 +53,13 @@ public class ScannerMain { private static final String SCANNER_APP_VERSION_KEY = "sonar.scanner.appVersion"; public static void main(String... args) { - System.exit(run(System.in)); + System.exit(run(System.in, System.out)); } - public static int run(InputStream in) { - try { + public static int run(InputStream in, OutputStream out) { + try (var ignored = new JGitCleanupService()) { + configureLogOutput(out); + LOG.info("Starting SonarScanner Engine..."); LOG.atInfo().log(ScannerMain::java); @@ -67,9 +73,11 @@ public class ScannerMain { LOG.info("SonarScanner Engine completed successfully"); return 0; - } catch (Exception e) { - handleException(e); + } catch (Throwable throwable) { + handleException(throwable); return 1; + } finally { + stopLogback(); } } @@ -87,30 +95,28 @@ public class ScannerMain { return sb.toString(); } - private static void handleException(Exception e) { - var messageException = unwrapMessageException(e); + private static void handleException(Throwable throwable) { + var messageException = unwrapMessageException(throwable); if (messageException.isPresent()) { // Don't show the stacktrace for a message exception to not pollute the logs if (LoggerFactory.getLogger(ScannerMain.class).isDebugEnabled()) { - LOG.error(messageException.get(), e); + LOG.error(messageException.get(), throwable); } else { LOG.error(messageException.get()); } } else { - LOG.error("Error during SonarScanner Engine execution", e); + LOG.error("Error during SonarScanner Engine execution", throwable); } } - private static Optional<String> unwrapMessageException(Exception t) { - Throwable y = t; - do { - if (y instanceof MessageException messageException) { - return Optional.of(messageException.getMessage()); - } - y = y.getCause(); - } while (y != null); - - return Optional.empty(); + private static Optional<String> unwrapMessageException(@Nullable Throwable throwable) { + if (throwable == null) { + return Optional.empty(); + } else if (throwable instanceof MessageException messageException) { + return Optional.of(messageException.getMessage()); + } else { + return unwrapMessageException(throwable.getCause()); + } } private static @NotNull Map<String, String> parseInputProperties(InputStream in) { @@ -157,6 +163,28 @@ public class ScannerMain { rootLogger.setLevel(Level.toLevel(verbose ? LEVEL_ROOT_VERBOSE : LEVEL_ROOT_DEFAULT)); } + private static void configureLogOutput(OutputStream out) { + var loggerContext = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory(); + var encoder = new ScannerLogbackEncoder(); + encoder.setContext(loggerContext); + encoder.start(); + + var appender = new OutputStreamAppender<ILoggingEvent>(); + appender.setEncoder(encoder); + appender.setContext(loggerContext); + appender.setOutputStream(out); + appender.start(); + + var rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME); + rootLogger.addAppender(appender); + rootLogger.setLevel(Level.toLevel(LEVEL_ROOT_DEFAULT)); + } + + private static void stopLogback() { + var loggerContext = (ch.qos.logback.classic.LoggerContext) LoggerFactory.getILoggerFactory(); + loggerContext.stop(); + } + private static class Input { @SerializedName("scannerProperties") private List<ScannerProperty> scannerProperties; diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java index 9c41891dbab..133f4387856 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/SpringScannerContainer.java @@ -99,7 +99,6 @@ import org.sonar.scanner.scan.InputModuleHierarchyProvider; import org.sonar.scanner.scan.InputProjectProvider; import org.sonar.scanner.scan.ModuleIndexer; import org.sonar.scanner.scan.MutableProjectReactorProvider; -import org.sonar.scanner.scan.MutableProjectSettings; import org.sonar.scanner.scan.ProjectBuildersExecutor; import org.sonar.scanner.scan.ProjectConfigurationProvider; import org.sonar.scanner.scan.ProjectLock; @@ -116,6 +115,7 @@ import org.sonar.scanner.scan.branch.BranchType; import org.sonar.scanner.scan.branch.ProjectBranchesProvider; import org.sonar.scanner.scan.filesystem.DefaultProjectFileSystem; import org.sonar.scanner.scan.filesystem.FilePreprocessor; +import org.sonar.scanner.scan.filesystem.HiddenFilesProjectData; import org.sonar.scanner.scan.filesystem.InputComponentStore; import org.sonar.scanner.scan.filesystem.LanguageDetection; import org.sonar.scanner.scan.filesystem.MetadataGenerator; @@ -200,6 +200,7 @@ public class SpringScannerContainer extends SpringComponentContainer { FilePreprocessor.class, ProjectFilePreprocessor.class, ProjectExclusionFilters.class, + HiddenFilesProjectData.class, // rules new ActiveRulesProvider(), @@ -226,7 +227,6 @@ public class SpringScannerContainer extends SpringComponentContainer { ContextPropertiesCache.class, TelemetryCache.class, - MutableProjectSettings.class, SonarGlobalPropertiesFilter.class, ProjectConfigurationProvider.class, diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/sarif/RulesSeverityDetector.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/sarif/RulesSeverityDetector.java index 703cd038fd0..d4f22ad9704 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/sarif/RulesSeverityDetector.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/sarif/RulesSeverityDetector.java @@ -97,8 +97,11 @@ public class RulesSeverityDetector { } private static Map<String, Result.Level> getDriverDefinedRuleSeverities(Run run) { - return run.getTool().getDriver().getRules() - .stream() + Set<ReportingDescriptor> rules = run.getTool().getDriver().getRules(); + if (rules == null) { + return emptyMap(); + } + return rules.stream() .filter(RulesSeverityDetector::hasRuleDefinedLevel) .collect(toMap(ReportingDescriptor::getId, x -> Result.Level.valueOf(x.getDefaultConfiguration().getLevel().name()))); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/sarif/RunMapper.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/sarif/RunMapper.java index 5b9abf383cf..bdf5a9a1114 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/sarif/RunMapper.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/externalissue/sarif/RunMapper.java @@ -83,7 +83,7 @@ public class RunMapper { private List<NewAdHocRule> toNewAdHocRules(Run run, String driverName, Map<String, Result.Level> ruleSeveritiesByRuleId, Map<String, Result.Level> ruleSeveritiesByRuleIdForNewCCT) { - Set<ReportingDescriptor> driverRules = run.getTool().getDriver().getRules(); + Set<ReportingDescriptor> driverRules = Optional.ofNullable(run.getTool().getDriver().getRules()).orElse(Set.of()); Set<ReportingDescriptor> extensionRules = hasExtensions(run.getTool()) ? run.getTool().getExtensions().stream().filter(RunMapper::hasRules).flatMap(extension -> extension.getRules().stream()).collect(toSet()) : Set.of(); diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java index 088ebfb0052..d9eed8bedc8 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/http/ScannerWsClientProvider.java @@ -87,7 +87,7 @@ public class ScannerWsClientProvider { String responseTimeout = defaultIfBlank(scannerProps.property(SONAR_SCANNER_RESPONSE_TIMEOUT), valueOf(DEFAULT_RESPONSE_TIMEOUT)); String envVarToken = defaultIfBlank(system.envVariable(TOKEN_ENV_VARIABLE), null); String token = defaultIfBlank(scannerProps.property(TOKEN_PROPERTY), envVarToken); - String login = defaultIfBlank(scannerProps.property(CoreProperties.LOGIN), token); + String login = defaultIfBlank(token, scannerProps.property(CoreProperties.LOGIN)); boolean skipSystemTrustMaterial = Boolean.parseBoolean(defaultIfBlank(scannerProps.property(SKIP_SYSTEM_TRUST_MATERIAL), "false")); var sslContext = configureSsl(parseSslConfig(scannerProps, sonarUserHome), system, skipSystemTrustMaterial); connectorBuilder diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java index 51bb79c9d23..24db0ddec64 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliCacheService.java @@ -25,6 +25,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.io.UncheckedIOException; import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.Path; @@ -139,7 +140,7 @@ public class CliCacheService { String checksum = metadataResponse.sha256(); // If we have a matching checksum dir with the existing CLI file, then we are up to date. if (!cachedCliFile(checksum).exists()) { - LOG.debug("CLI checksum mismatch"); + LOG.debug("SCA CLI update detected"); downloadCli(metadataResponse.id(), checksum); telemetryCache.put("scanner.sca.get.cli.cache.hit", "false"); } else { @@ -175,8 +176,8 @@ public class CliCacheService { }.getType(); return new Gson().fromJson(reader, listOfMetadata); } - } catch (Exception e) { - throw new IllegalStateException("Unable to load CLI metadata", e); + } catch (IOException e) { + throw new UncheckedIOException(e); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java index 8a8e90cce25..6b3418a8f6a 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/CliService.java @@ -29,20 +29,22 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Stream; import javax.annotation.Nullable; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVPrinter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.event.Level; +import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.api.platform.Server; import org.sonar.api.utils.System2; import org.sonar.core.util.ProcessWrapperFactory; import org.sonar.scanner.config.DefaultConfiguration; import org.sonar.scanner.repository.TelemetryCache; +import org.sonar.scanner.scan.filesystem.ProjectExclusionFilters; import org.sonar.scanner.scm.ScmConfiguration; import org.sonar.scm.git.JGitUtils; @@ -54,37 +56,42 @@ import org.sonar.scm.git.JGitUtils; */ public class CliService { private static final Logger LOG = LoggerFactory.getLogger(CliService.class); - public static final String EXCLUDED_MANIFESTS_PROP_KEY = "sonar.sca.excludedManifests"; + public static final String SCA_EXCLUSIONS_KEY = "sonar.sca.exclusions"; + public static final String LEGACY_SCA_EXCLUSIONS_KEY = "sonar.sca.excludedManifests"; private final ProcessWrapperFactory processWrapperFactory; private final TelemetryCache telemetryCache; private final System2 system2; private final Server server; private final ScmConfiguration scmConfiguration; + private final ProjectExclusionFilters projectExclusionFilters; - public CliService(ProcessWrapperFactory processWrapperFactory, TelemetryCache telemetryCache, System2 system2, Server server, ScmConfiguration scmConfiguration) { + public CliService(ProcessWrapperFactory processWrapperFactory, TelemetryCache telemetryCache, System2 system2, Server server, ScmConfiguration scmConfiguration, + ProjectExclusionFilters projectExclusionFilters) { this.processWrapperFactory = processWrapperFactory; this.telemetryCache = telemetryCache; this.system2 = system2; this.server = server; this.scmConfiguration = scmConfiguration; + this.projectExclusionFilters = projectExclusionFilters; } - public File generateManifestsZip(DefaultInputModule module, File cliExecutable, DefaultConfiguration configuration) throws IOException, IllegalStateException { + public File generateManifestsArchive(DefaultInputModule module, File cliExecutable, DefaultConfiguration configuration) throws IOException, IllegalStateException { long startTime = system2.now(); boolean success = false; try { - String zipName = "dependency-files.zip"; - Path zipPath = module.getWorkDir().resolve(zipName); + String archiveName = "dependency-files.tar.xz"; + Path archivePath = module.getWorkDir().resolve(archiveName); List<String> args = new ArrayList<>(); args.add(cliExecutable.getAbsolutePath()); args.add("projects"); args.add("save-lockfiles"); - args.add("--zip"); - args.add("--zip-filename"); - args.add(zipPath.toAbsolutePath().toString()); + args.add("--xz"); + args.add("--xz-filename"); + args.add(archivePath.toAbsolutePath().toString()); args.add("--directory"); args.add(module.getBaseDir().toString()); + args.add("--recursive"); String excludeFlag = getExcludeFlag(module, configuration); if (excludeFlag != null) { @@ -92,8 +99,7 @@ public class CliService { args.add(excludeFlag); } - boolean scaDebug = configuration.getBoolean("sonar.sca.debug").orElse(false); - if (LOG.isDebugEnabled() || scaDebug) { + if (LOG.isDebugEnabled()) { LOG.info("Setting CLI to debug mode"); args.add("--debug"); } @@ -104,18 +110,16 @@ public class CliService { envProperties.put("TIDELIFT_ALLOW_MANIFEST_FAILURES", "1"); envProperties.put("TIDELIFT_CLI_INSIDE_SCANNER_ENGINE", "1"); envProperties.put("TIDELIFT_CLI_SQ_SERVER_VERSION", server.getVersion()); - // EXCLUDED_MANIFESTS_PROP_KEY is a special case which we handle via --args, not environment variables - Set<String> ignoredProperties = Set.of(EXCLUDED_MANIFESTS_PROP_KEY); - envProperties.putAll(ScaProperties.buildFromScannerProperties(configuration, ignoredProperties)); + envProperties.putAll(ScaProperties.buildFromScannerProperties(configuration)); LOG.info("Running command: {}", args); LOG.info("Environment properties: {}", envProperties); Consumer<String> logConsumer = LOG.atLevel(Level.INFO)::log; processWrapperFactory.create(module.getWorkDir(), logConsumer, logConsumer, envProperties, args.toArray(new String[0])).execute(); - LOG.info("Generated manifests zip file: {}", zipName); + LOG.info("Generated manifests archive file: {}", archiveName); success = true; - return zipPath.toFile(); + return archivePath.toFile(); } finally { telemetryCache.put("scanner.sca.execution.cli.duration", String.valueOf(system2.now() - startTime)); telemetryCache.put("scanner.sca.execution.cli.success", String.valueOf(success)); @@ -123,7 +127,7 @@ public class CliService { } private @Nullable String getExcludeFlag(DefaultInputModule module, DefaultConfiguration configuration) throws IOException { - List<String> configExcludedPaths = getConfigExcludedPaths(configuration); + List<String> configExcludedPaths = getConfigExcludedPaths(configuration, projectExclusionFilters); List<String> scmIgnoredPaths = getScmIgnoredPaths(module); ArrayList<String> mergedExclusionPaths = new ArrayList<>(); @@ -143,12 +147,15 @@ public class CliService { return toCsvString(mergedExclusionPaths); } - private static List<String> getConfigExcludedPaths(DefaultConfiguration configuration) { - String[] excludedPaths = configuration.getStringArray(EXCLUDED_MANIFESTS_PROP_KEY); - if (excludedPaths == null) { - return List.of(); - } - return Arrays.stream(excludedPaths).toList(); + private static List<String> getConfigExcludedPaths(DefaultConfiguration configuration, ProjectExclusionFilters projectExclusionFilters) { + String[] sonarExclusions = projectExclusionFilters.getExclusionsConfig(InputFile.Type.MAIN); + String[] scaExclusions = configuration.getStringArray(SCA_EXCLUSIONS_KEY); + String[] scaExclusionsLegacy = configuration.getStringArray(LEGACY_SCA_EXCLUSIONS_KEY); + + return Stream.of(sonarExclusions, scaExclusions, scaExclusionsLegacy) + .flatMap(Arrays::stream) + .distinct() + .toList(); } private List<String> getScmIgnoredPaths(DefaultInputModule module) { @@ -170,7 +177,13 @@ public class CliService { } return scmIgnoredPaths.stream() .map(ignoredPathRel -> { - boolean isDirectory = Files.isDirectory(baseDirPath.resolve(ignoredPathRel)); + + boolean isDirectory = false; + try { + isDirectory = Files.isDirectory(baseDirPath.resolve(ignoredPathRel.replace("/", File.separator))); + } catch (java.nio.file.InvalidPathException e) { + // if it's not a valid path, it's not a directory so we can just pass to the Tidelift CLI + } // Directories need to get turned into a glob for the Tidelift CLI return isDirectory ? (ignoredPathRel + "/**") : ignoredPathRel; }) diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaExecutor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaExecutor.java index 06142fadb8f..143e144c2dc 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaExecutor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaExecutor.java @@ -21,6 +21,8 @@ package org.sonar.scanner.sca; import java.io.File; import java.io.IOException; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.time.StopWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.internal.DefaultInputModule; @@ -65,18 +67,25 @@ public class ScaExecutor { return; } + var stopwatch = new StopWatch(); + stopwatch.start(); LOG.info("Checking for latest CLI"); File cliFile = cliCacheService.cacheCli(); LOG.info("Collecting manifests for the dependency analysis..."); if (cliFile.exists()) { try { - File generatedZip = cliService.generateManifestsZip(root, cliFile, configuration); + File generatedZip = cliService.generateManifestsArchive(root, cliFile, configuration); LOG.debug("Zip ready for report: {}", generatedZip); reportPublisher.getWriter().writeScaFile(generatedZip); LOG.debug("Manifest zip written to report"); } catch (IOException | IllegalStateException e) { LOG.error("Error gathering manifests", e); + } finally { + stopwatch.stop(); + if (LOG.isInfoEnabled()) { + LOG.info("Load SCA project dependencies (done) | time={}ms", stopwatch.getTime(TimeUnit.MILLISECONDS)); + } } } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java index 5c848b4ddbc..a697aef3e20 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sca/ScaProperties.java @@ -30,6 +30,13 @@ import org.sonar.scanner.config.DefaultConfiguration; public class ScaProperties { private static final Pattern sonarScaPropertyRegex = Pattern.compile("^sonar\\.sca\\.([a-zA-Z]+)$"); private static final String SONAR_SCA_PREFIX = "sonar.sca."; + private static final Set<String> IGNORED_PROPERTIES = Set.of( + // sonar.sca.exclusions is a special case which we handle when building --exclude + "sonar.sca.exclusions", + // excludedManifests is a special case which we handle when building --exclude + "sonar.sca.excludedManifests", + // keep recursive enabled to better match sonar-scanner behavior + "sonar.sca.recursiveManifestSearch"); private ScaProperties() { } @@ -46,22 +53,16 @@ public class ScaProperties { * { "sonar.someOtherProperty" : "value" } returns an empty map * * @param configuration the scanner configuration possibly containing sonar.sca.* properties - * @param ignoredPropertyNames property names that should not be processed as a property * @return a map of Tidelift CLI compatible environment variable names to their configuration values */ - public static Map<String, String> buildFromScannerProperties(DefaultConfiguration configuration, Set<String> ignoredPropertyNames) { + public static Map<String, String> buildFromScannerProperties(DefaultConfiguration configuration) { HashMap<String, String> props = new HashMap<>(configuration.getProperties()); - // recursive mode defaults to true - if (!props.containsKey("sonar.sca.recursiveManifestSearch")) { - props.put("sonar.sca.recursiveManifestSearch", "true"); - } - return props .entrySet() .stream() .filter(entry -> entry.getKey().startsWith(SONAR_SCA_PREFIX)) - .filter(entry -> !ignoredPropertyNames.contains(entry.getKey())) + .filter(entry -> !IGNORED_PROPERTIES.contains(entry.getKey())) .collect(Collectors.toMap(entry -> convertPropToEnvVariable(entry.getKey()), Map.Entry::getValue)); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/MutableModuleSettings.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/MutableModuleSettings.java deleted file mode 100644 index 15912f8a510..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/MutableModuleSettings.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2025 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.scanner.scan; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import jakarta.annotation.Priority; -import org.sonar.api.config.internal.Settings; - -import static java.util.Objects.requireNonNull; - -/** - * @deprecated since 6.5 {@link ModuleConfiguration} used to be mutable, so keep a mutable copy for backward compatibility. - */ -@Deprecated -@Priority(1) -public class MutableModuleSettings extends Settings { - - private final Map<String, String> properties = new HashMap<>(); - - public MutableModuleSettings(ModuleConfiguration config) { - super(config.getDefinitions(), config.getEncryption()); - addProperties(config.getProperties()); - } - - @Override - protected Optional<String> get(String key) { - return Optional.ofNullable(properties.get(key)); - } - - @Override - protected void set(String key, String value) { - properties.put( - requireNonNull(key, "key can't be null"), - requireNonNull(value, "value can't be null").trim()); - } - - @Override - protected void remove(String key) { - properties.remove(key); - } - - @Override - public Map<String, String> getProperties() { - return properties; - } -} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/MutableProjectSettings.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/MutableProjectSettings.java deleted file mode 100644 index df24cbe81e5..00000000000 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/MutableProjectSettings.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2025 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.scanner.scan; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import org.sonar.api.config.internal.Settings; -import org.sonar.scanner.bootstrap.GlobalConfiguration; - -import jakarta.annotation.Priority; - -import static java.util.Objects.requireNonNull; - -/** - * @deprecated since 6.5 {@link ProjectConfiguration} used to be mutable, so keep a mutable copy for backward compatibility. - */ -@Deprecated -@Priority(2) -public class MutableProjectSettings extends Settings { - - private final Map<String, String> properties = new HashMap<>(); - - public MutableProjectSettings(GlobalConfiguration globalConfig) { - super(globalConfig.getDefinitions(), globalConfig.getEncryption()); - addProperties(globalConfig.getProperties()); - } - - public void complete(ProjectConfiguration projectConfig) { - addProperties(projectConfig.getProperties()); - } - - @Override - protected Optional<String> get(String key) { - return Optional.ofNullable(properties.get(key)); - } - - @Override - protected void set(String key, String value) { - properties.put( - requireNonNull(key, "key can't be null"), - requireNonNull(value, "value can't be null").trim()); - } - - @Override - protected void remove(String key) { - properties.remove(key); - } - - @Override - public Map<String, String> getProperties() { - return properties; - } -} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectConfigurationProvider.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectConfigurationProvider.java index c12ec245924..e5543d4f9c5 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectConfigurationProvider.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/ProjectConfigurationProvider.java @@ -26,7 +26,6 @@ import org.sonar.scanner.bootstrap.GlobalConfiguration; import org.sonar.scanner.bootstrap.GlobalServerSettings; import org.springframework.context.annotation.Bean; - public class ProjectConfigurationProvider { private final SonarGlobalPropertiesFilter sonarGlobalPropertiesFilter; @@ -37,7 +36,7 @@ public class ProjectConfigurationProvider { @Bean("ProjectConfiguration") public ProjectConfiguration provide(DefaultInputProject project, GlobalConfiguration globalConfig, GlobalServerSettings globalServerSettings, - ProjectServerSettings projectServerSettings, MutableProjectSettings projectSettings) { + ProjectServerSettings projectServerSettings) { Map<String, String> settings = new LinkedHashMap<>(); settings.putAll(globalServerSettings.properties()); settings.putAll(projectServerSettings.properties()); @@ -45,10 +44,7 @@ public class ProjectConfigurationProvider { settings = sonarGlobalPropertiesFilter.enforceOnlyServerSideSonarGlobalPropertiesAreUsed(settings, globalServerSettings.properties()); - ProjectConfiguration projectConfig = new ProjectConfiguration(globalConfig.getDefinitions(), globalConfig.getEncryption(), settings); - projectSettings.complete(projectConfig); - return projectConfig; + return new ProjectConfiguration(globalConfig.getDefinitions(), globalConfig.getEncryption(), settings); } - } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/SpringModuleScanContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/SpringModuleScanContainer.java index 4315c762481..8ddb889912d 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/SpringModuleScanContainer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/SpringModuleScanContainer.java @@ -54,7 +54,6 @@ public class SpringModuleScanContainer extends SpringComponentContainer { add( module.definition(), module, - MutableModuleSettings.class, SonarGlobalPropertiesFilter.class, ModuleConfigurationProvider.class, @@ -68,8 +67,7 @@ public class SpringModuleScanContainer extends SpringComponentContainer { ModuleSensorOptimizer.class, ModuleSensorContext.class, - ModuleSensorExtensionDictionary.class - ); + ModuleSensorExtensionDictionary.class); } private void addExtensions() { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DirectoryFileVisitor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DirectoryFileVisitor.java index 314e923ce71..242bc015574 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DirectoryFileVisitor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/DirectoryFileVisitor.java @@ -24,17 +24,15 @@ import java.nio.file.AccessDeniedException; import java.nio.file.FileSystemLoopException; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; -import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.DosFileAttributes; -import org.apache.commons.lang3.SystemUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultInputModule; import org.sonar.scanner.fs.InputModuleHierarchy; +import org.sonar.scanner.scan.ModuleConfiguration; public class DirectoryFileVisitor implements FileVisitor<Path> { @@ -43,27 +41,31 @@ public class DirectoryFileVisitor implements FileVisitor<Path> { private final FileVisitAction fileVisitAction; private final DefaultInputModule module; private final ModuleExclusionFilters moduleExclusionFilters; - private final InputModuleHierarchy inputModuleHierarchy; private final InputFile.Type type; + private final HiddenFilesVisitorHelper hiddenFilesVisitorHelper; - DirectoryFileVisitor(FileVisitAction fileVisitAction, DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, - InputModuleHierarchy inputModuleHierarchy, InputFile.Type type) { + DirectoryFileVisitor(FileVisitAction fileVisitAction, DefaultInputModule module, ModuleConfiguration moduleConfig, ModuleExclusionFilters moduleExclusionFilters, + InputModuleHierarchy inputModuleHierarchy, InputFile.Type type, HiddenFilesProjectData hiddenFilesProjectData) { this.fileVisitAction = fileVisitAction; this.module = module; this.moduleExclusionFilters = moduleExclusionFilters; this.inputModuleHierarchy = inputModuleHierarchy; this.type = type; + this.hiddenFilesVisitorHelper = new HiddenFilesVisitorHelper(hiddenFilesProjectData, module, moduleConfig); } @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - return isHidden(dir) ? FileVisitResult.SKIP_SUBTREE : FileVisitResult.CONTINUE; + if (hiddenFilesVisitorHelper.shouldVisitDir(dir)) { + return FileVisitResult.CONTINUE; + } + return FileVisitResult.SKIP_SUBTREE; } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - if (!Files.isHidden(file)) { + if (hiddenFilesVisitorHelper.shouldVisitFile(file)) { fileVisitAction.execute(file); } return FileVisitResult.CONTINUE; @@ -129,25 +131,12 @@ public class DirectoryFileVisitor implements FileVisitor<Path> { @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) { + hiddenFilesVisitorHelper.exitDirectory(dir); return FileVisitResult.CONTINUE; } - private static boolean isHidden(Path path) throws IOException { - if (SystemUtils.IS_OS_WINDOWS) { - try { - DosFileAttributes dosFileAttributes = Files.readAttributes(path, DosFileAttributes.class, LinkOption.NOFOLLOW_LINKS); - return dosFileAttributes.isHidden(); - } catch (UnsupportedOperationException e) { - return path.toFile().isHidden(); - } - } else { - return Files.isHidden(path); - } - } - @FunctionalInterface interface FileVisitAction { void execute(Path file) throws IOException; } } - diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java index 7f31c949132..0961edbd985 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FileIndexer.java @@ -63,12 +63,13 @@ public class FileIndexer { private final ModuleRelativePathWarner moduleRelativePathWarner; private final InputFileFilterRepository inputFileFilterRepository; private final Languages languages; + private final HiddenFilesProjectData hiddenFilesProjectData; public FileIndexer(DefaultInputProject project, ScannerComponentIdGenerator scannerComponentIdGenerator, InputComponentStore componentStore, ProjectCoverageAndDuplicationExclusions projectCoverageAndDuplicationExclusions, IssueExclusionsLoader issueExclusionsLoader, MetadataGenerator metadataGenerator, SensorStrategy sensorStrategy, LanguageDetection languageDetection, ScanProperties properties, ScmChangedFiles scmChangedFiles, StatusDetection statusDetection, ModuleRelativePathWarner moduleRelativePathWarner, - InputFileFilterRepository inputFileFilterRepository, Languages languages) { + InputFileFilterRepository inputFileFilterRepository, Languages languages, HiddenFilesProjectData hiddenFilesProjectData) { this.project = project; this.scannerComponentIdGenerator = scannerComponentIdGenerator; this.componentStore = componentStore; @@ -83,15 +84,18 @@ public class FileIndexer { this.moduleRelativePathWarner = moduleRelativePathWarner; this.inputFileFilterRepository = inputFileFilterRepository; this.languages = languages; + this.hiddenFilesProjectData = hiddenFilesProjectData; } - void indexFile(DefaultInputModule module, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, Path sourceFile, - Type type, ProgressReport progressReport) { + void indexFile(DefaultInputModule module, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, Path sourceFile, Type type, + ProgressReport progressReport) { Path projectRelativePath = project.getBaseDir().relativize(sourceFile); Path moduleRelativePath = module.getBaseDir().relativize(sourceFile); // This should be fast; language should be cached from preprocessing step Language language = langDetection.language(sourceFile, projectRelativePath); + // cached from directory file visitation, after querying the data is removed to reduce memory consumption + boolean isHidden = hiddenFilesProjectData.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(sourceFile, module); DefaultIndexedFile indexedFile = new DefaultIndexedFile( sourceFile, @@ -102,11 +106,12 @@ public class FileIndexer { language != null ? language.key() : null, scannerComponentIdGenerator.getAsInt(), sensorStrategy, - scmChangedFiles.getOldRelativeFilePath(sourceFile)); + scmChangedFiles.getOldRelativeFilePath(sourceFile), + isHidden); DefaultInputFile inputFile = new DefaultInputFile(indexedFile, f -> metadataGenerator.setMetadata(module.key(), f, module.getEncoding()), f -> f.setStatus(statusDetection.findStatusFromScm(f))); - if (language != null && isPublishAllFiles(language.key())) { + if (!isHidden && language != null && isPublishAllFiles(language.key())) { inputFile.setPublished(true); } if (!accept(inputFile)) { diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FilePreprocessor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FilePreprocessor.java index 544fe46c43b..a87c5f11fc9 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FilePreprocessor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/FilePreprocessor.java @@ -147,22 +147,35 @@ public class FilePreprocessor { return true; } - Path target = Files.readSymbolicLink(absolutePath); - if (!Files.exists(target)) { + Optional<Path> target = resolvePathToTarget(absolutePath); + if (target.isEmpty() || !Files.exists(target.get())) { LOG.warn("File '{}' is ignored. It is a symbolic link targeting a file that does not exist.", absolutePath); return false; } - if (!target.startsWith(project.getBaseDir())) { + if (!target.get().startsWith(project.getBaseDir())) { LOG.warn("File '{}' is ignored. It is a symbolic link targeting a file not located in project basedir.", absolutePath); return false; } - if (!target.startsWith(moduleBaseDirectory)) { + if (!target.get().startsWith(moduleBaseDirectory)) { LOG.info("File '{}' is ignored. It is a symbolic link targeting a file not located in module basedir.", absolutePath); return false; } return true; } + + private static Optional<Path> resolvePathToTarget(Path symbolicLinkAbsolutePath) throws IOException { + Path target = Files.readSymbolicLink(symbolicLinkAbsolutePath); + if (target.isAbsolute()) { + return Optional.of(target); + } + + try { + return Optional.of(symbolicLinkAbsolutePath.getParent().resolve(target).toRealPath(LinkOption.NOFOLLOW_LINKS).toAbsolutePath().normalize()); + } catch (IOException e) { + return Optional.empty(); + } + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectData.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectData.java new file mode 100644 index 00000000000..d779a054455 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesProjectData.java @@ -0,0 +1,77 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.scan.filesystem; + +import java.io.IOException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.SystemUtils; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.scanner.bootstrap.SonarUserHome; + +public class HiddenFilesProjectData { + + final Map<DefaultInputModule, Set<Path>> hiddenFilesByModule = new HashMap<>(); + private final SonarUserHome sonarUserHome; + private Path cachedSonarUserHomePath; + + public HiddenFilesProjectData(SonarUserHome sonarUserHome) { + this.sonarUserHome = sonarUserHome; + } + + public void markAsHiddenFile(Path file, DefaultInputModule module) { + hiddenFilesByModule.computeIfAbsent(module, k -> new HashSet<>()).add(file); + } + + /** + * To alleviate additional strain on the memory, we remove the visibility information for <code>hiddenFilesByModule</code> mapdirectly after querying, + * as we don't need it afterward. + */ + public boolean getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(Path file, DefaultInputModule module) { + Set<Path> hiddenFilesPerModule = hiddenFilesByModule.get(module); + if (hiddenFilesPerModule != null) { + return hiddenFilesPerModule.remove(file); + } + return false; + } + + public Path getCachedSonarUserHomePath() throws IOException { + if (cachedSonarUserHomePath == null) { + cachedSonarUserHomePath = resolveRealPath(sonarUserHome.getPath()); + } + return cachedSonarUserHomePath; + } + + public void clearHiddenFilesData() { + // Allowing the GC to collect the map, should only be done after all indexing is complete + hiddenFilesByModule.clear(); + } + + public Path resolveRealPath(Path path) throws IOException { + if (SystemUtils.IS_OS_WINDOWS) { + return path.toRealPath(LinkOption.NOFOLLOW_LINKS).toAbsolutePath().normalize(); + } + return path; + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesVisitorHelper.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesVisitorHelper.java new file mode 100644 index 00000000000..607a859ef44 --- /dev/null +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/HiddenFilesVisitorHelper.java @@ -0,0 +1,112 @@ +/* + * SonarQube + * Copyright (C) 2009-2025 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.scanner.scan.filesystem; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.attribute.DosFileAttributes; +import org.apache.commons.lang3.SystemUtils; +import org.sonar.api.batch.fs.internal.DefaultInputModule; +import org.sonar.scanner.scan.ModuleConfiguration; + +public class HiddenFilesVisitorHelper { + + private static final String EXCLUDE_HIDDEN_FILES_PROPERTY = "sonar.scanner.excludeHiddenFiles"; + private final HiddenFilesProjectData hiddenFilesProjectData; + private final DefaultInputModule module; + final boolean excludeHiddenFiles; + private Path moduleWorkDir; + Path rootHiddenDir; + + public HiddenFilesVisitorHelper(HiddenFilesProjectData hiddenFilesProjectData, DefaultInputModule module, ModuleConfiguration moduleConfig) { + this.hiddenFilesProjectData = hiddenFilesProjectData; + this.module = module; + this.excludeHiddenFiles = moduleConfig.getBoolean(EXCLUDE_HIDDEN_FILES_PROPERTY).orElse(false); + } + + public boolean shouldVisitDir(Path path) throws IOException { + boolean isHidden = isHiddenDir(path); + + if (isHidden && (excludeHiddenFiles || isExcludedHiddenDirectory(path))) { + return false; + } + if (isHidden) { + enterHiddenDirectory(path); + } + return true; + } + + private boolean isExcludedHiddenDirectory(Path path) throws IOException { + return getCachedModuleWorkDir().equals(path) || hiddenFilesProjectData.getCachedSonarUserHomePath().equals(path); + } + + void enterHiddenDirectory(Path dir) { + if (!insideHiddenDirectory()) { + rootHiddenDir = dir; + } + } + + public void exitDirectory(Path path) { + if (insideHiddenDirectory() && rootHiddenDir.equals(path)) { + resetRootHiddenDir(); + } + } + + void resetRootHiddenDir() { + this.rootHiddenDir = null; + } + + public boolean shouldVisitFile(Path path) throws IOException { + boolean isHidden = insideHiddenDirectory() || Files.isHidden(path); + + if (!excludeHiddenFiles && isHidden) { + hiddenFilesProjectData.markAsHiddenFile(path, module); + } + + return !excludeHiddenFiles || !isHidden; + } + + private Path getCachedModuleWorkDir() throws IOException { + if (moduleWorkDir == null) { + moduleWorkDir = hiddenFilesProjectData.resolveRealPath(module.getWorkDir()); + } + return moduleWorkDir; + } + + // visible for testing + boolean insideHiddenDirectory() { + return rootHiddenDir != null; + } + + protected static boolean isHiddenDir(Path path) throws IOException { + if (SystemUtils.IS_OS_WINDOWS) { + try { + DosFileAttributes dosFileAttributes = Files.readAttributes(path, DosFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + return dosFileAttributes.isHidden(); + } catch (UnsupportedOperationException e) { + return path.toFile().isHidden(); + } + } else { + return Files.isHidden(path); + } + } +} diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleInputComponentStore.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleInputComponentStore.java index 6ef26dafd07..68b6d1db580 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleInputComponentStore.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ModuleInputComponentStore.java @@ -19,12 +19,15 @@ */ package org.sonar.scanner.scan.filesystem; +import java.util.Set; import java.util.SortedSet; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import org.sonar.api.batch.ScannerSide; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.InputModule; -import org.sonar.api.batch.fs.internal.SensorStrategy; import org.sonar.api.batch.fs.internal.DefaultFileSystem; +import org.sonar.api.batch.fs.internal.SensorStrategy; @ScannerSide public class ModuleInputComponentStore extends DefaultFileSystem.Cache { @@ -73,11 +76,29 @@ public class ModuleInputComponentStore extends DefaultFileSystem.Cache { @Override public Iterable<InputFile> getFilesByName(String filename) { - return inputComponentStore.getFilesByName(filename); + Iterable<InputFile> allFilesByName = inputComponentStore.getFilesByName(filename); + if (strategy.isGlobal()) { + return allFilesByName; + } + + return filterByModule(allFilesByName); } @Override public Iterable<InputFile> getFilesByExtension(String extension) { - return inputComponentStore.getFilesByExtension(extension); + Iterable<InputFile> allFilesByExtension = inputComponentStore.getFilesByExtension(extension); + if (strategy.isGlobal()) { + return allFilesByExtension; + } + + return filterByModule(allFilesByExtension); + } + + private Iterable<InputFile> filterByModule(Iterable<InputFile> projectInputFiles) { + Set<InputFile> projectInputFilesSet = StreamSupport.stream(projectInputFiles.spliterator(), false) + .collect(Collectors.toSet()); + return StreamSupport.stream(inputComponentStore.filesByModule(moduleKey).spliterator(), false) + .filter(projectInputFilesSet::contains) + .toList(); } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java index 5daa384d3ac..9c969f6ae20 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/MutableFileSystem.java @@ -25,35 +25,54 @@ import org.sonar.api.batch.fs.FilePredicates; import org.sonar.api.batch.fs.InputFile; import org.sonar.api.batch.fs.internal.DefaultFileSystem; import org.sonar.api.batch.fs.internal.predicates.ChangedFilePredicate; +import org.sonar.api.batch.fs.internal.predicates.NonHiddenFilesPredicate; public class MutableFileSystem extends DefaultFileSystem { - private boolean restrictToChangedFiles = false; + + boolean restrictToChangedFiles = false; + boolean allowHiddenFileAnalysis = false; public MutableFileSystem(Path baseDir, Cache cache, FilePredicates filePredicates) { super(baseDir, cache, filePredicates); } - public MutableFileSystem(Path baseDir) { + MutableFileSystem(Path baseDir) { super(baseDir); } @Override public Iterable<InputFile> inputFiles(FilePredicate requestPredicate) { - if (restrictToChangedFiles) { - return super.inputFiles(new ChangedFilePredicate(requestPredicate)); - } - return super.inputFiles(requestPredicate); + return super.inputFiles(applyAdditionalPredicate(requestPredicate)); } @Override public InputFile inputFile(FilePredicate requestPredicate) { + return super.inputFile(applyAdditionalPredicate(requestPredicate)); + } + + private FilePredicate applyAdditionalPredicate(FilePredicate requestPredicate) { + return applyHiddenFilePredicate(applyChangedFilePredicate(requestPredicate)); + } + + private FilePredicate applyHiddenFilePredicate(FilePredicate predicate) { + if (allowHiddenFileAnalysis) { + return predicate; + } + return predicates().and(new NonHiddenFilesPredicate(), predicate); + } + + private FilePredicate applyChangedFilePredicate(FilePredicate predicate) { if (restrictToChangedFiles) { - return super.inputFile(new ChangedFilePredicate(requestPredicate)); + return predicates().and(new ChangedFilePredicate(), predicate); } - return super.inputFile(requestPredicate); + return predicate; } public void setRestrictToChangedFiles(boolean restrictToChangedFiles) { this.restrictToChangedFiles = restrictToChangedFiles; } + + public void setAllowHiddenFileAnalysis(boolean allowHiddenFileAnalysis) { + this.allowHiddenFileAnalysis = allowHiddenFileAnalysis; + } } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFileIndexer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFileIndexer.java index 97e449fcb26..c1349872c24 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFileIndexer.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFileIndexer.java @@ -62,6 +62,7 @@ public class ProjectFileIndexer { private final FileIndexer fileIndexer; private final ProjectFilePreprocessor projectFilePreprocessor; private final AnalysisWarnings analysisWarnings; + private final HiddenFilesProjectData hiddenFilesProjectData; private ProgressReport progressReport; @@ -69,7 +70,7 @@ public class ProjectFileIndexer { SonarGlobalPropertiesFilter sonarGlobalPropertiesFilter, InputModuleHierarchy inputModuleHierarchy, GlobalConfiguration globalConfig, GlobalServerSettings globalServerSettings, ProjectServerSettings projectServerSettings, FileIndexer fileIndexer, ProjectCoverageAndDuplicationExclusions projectCoverageAndDuplicationExclusions, - ProjectFilePreprocessor projectFilePreprocessor, AnalysisWarnings analysisWarnings) { + ProjectFilePreprocessor projectFilePreprocessor, AnalysisWarnings analysisWarnings, HiddenFilesProjectData hiddenFilesProjectData) { this.componentStore = componentStore; this.sonarGlobalPropertiesFilter = sonarGlobalPropertiesFilter; this.inputModuleHierarchy = inputModuleHierarchy; @@ -81,6 +82,7 @@ public class ProjectFileIndexer { this.projectCoverageAndDuplicationExclusions = projectCoverageAndDuplicationExclusions; this.projectFilePreprocessor = projectFilePreprocessor; this.analysisWarnings = analysisWarnings; + this.hiddenFilesProjectData = hiddenFilesProjectData; } public void index() { @@ -91,10 +93,10 @@ public class ProjectFileIndexer { projectCoverageAndDuplicationExclusions.log(" "); indexModulesRecursively(inputModuleHierarchy.root()); + hiddenFilesProjectData.clearHiddenFilesData(); int totalIndexed = componentStore.inputFiles().size(); - progressReport.stop(totalIndexed + " " + pluralizeFiles(totalIndexed) + " indexed"); - + progressReport.stopAndLogTotalTime(totalIndexed + " " + pluralizeFiles(totalIndexed) + " indexed"); } private void indexModulesRecursively(DefaultInputModule module) { @@ -118,15 +120,15 @@ public class ProjectFileIndexer { moduleCoverageAndDuplicationExclusions.log(" "); } List<Path> mainSourceDirsOrFiles = projectFilePreprocessor.getMainSourcesByModule(module); - indexFiles(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, mainSourceDirsOrFiles, Type.MAIN); + indexFiles(module, moduleConfig, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, mainSourceDirsOrFiles, Type.MAIN); projectFilePreprocessor.getTestSourcesByModule(module) - .ifPresent(tests -> indexFiles(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, tests, Type.TEST)); + .ifPresent(tests -> indexFiles(module, moduleConfig, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, tests, Type.TEST)); } private static void logPaths(String label, Path baseDir, List<Path> paths) { if (!paths.isEmpty()) { StringBuilder sb = new StringBuilder(label); - for (Iterator<Path> it = paths.iterator(); it.hasNext(); ) { + for (Iterator<Path> it = paths.iterator(); it.hasNext();) { Path file = it.next(); Optional<String> relativePathToBaseDir = PathResolver.relativize(baseDir, file); if (relativePathToBaseDir.isEmpty()) { @@ -148,12 +150,13 @@ public class ProjectFileIndexer { } } - private void indexFiles(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, + private void indexFiles(DefaultInputModule module, ModuleConfiguration moduleConfig, ModuleExclusionFilters moduleExclusionFilters, + ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, List<Path> sources, Type type) { try { for (Path dirOrFile : sources) { if (dirOrFile.toFile().isDirectory()) { - indexDirectory(module, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, dirOrFile, type); + indexDirectory(module, moduleConfig, moduleExclusionFilters, moduleCoverageAndDuplicationExclusions, dirOrFile, type); } else { fileIndexer.indexFile(module, moduleCoverageAndDuplicationExclusions, dirOrFile, type, progressReport); } @@ -163,18 +166,16 @@ public class ProjectFileIndexer { } } - private void indexDirectory(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, + private void indexDirectory(DefaultInputModule module, ModuleConfiguration moduleConfig, ModuleExclusionFilters moduleExclusionFilters, ModuleCoverageAndDuplicationExclusions moduleCoverageAndDuplicationExclusions, Path dirToIndex, Type type) throws IOException { Files.walkFileTree(dirToIndex.normalize(), Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new DirectoryFileVisitor(file -> fileIndexer.indexFile(module, moduleCoverageAndDuplicationExclusions, file, type, progressReport), - module, moduleExclusionFilters, inputModuleHierarchy, type)); + module, moduleConfig, moduleExclusionFilters, inputModuleHierarchy, type, hiddenFilesProjectData)); } private static String pluralizeFiles(int count) { return count == 1 ? "file" : "files"; } - - } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFilePreprocessor.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFilePreprocessor.java index 033ab56d3d4..3e7b655589c 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFilePreprocessor.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/filesystem/ProjectFilePreprocessor.java @@ -66,6 +66,7 @@ public class ProjectFilePreprocessor { private final LanguageDetection languageDetection; private final FilePreprocessor filePreprocessor; private final ProjectExclusionFilters projectExclusionFilters; + private final HiddenFilesProjectData hiddenFilesProjectData; private final SonarGlobalPropertiesFilter sonarGlobalPropertiesFilter; @@ -79,7 +80,7 @@ public class ProjectFilePreprocessor { public ProjectFilePreprocessor(AnalysisWarnings analysisWarnings, ScmConfiguration scmConfiguration, InputModuleHierarchy inputModuleHierarchy, GlobalConfiguration globalConfig, GlobalServerSettings globalServerSettings, ProjectServerSettings projectServerSettings, LanguageDetection languageDetection, FilePreprocessor filePreprocessor, - ProjectExclusionFilters projectExclusionFilters, SonarGlobalPropertiesFilter sonarGlobalPropertiesFilter) { + ProjectExclusionFilters projectExclusionFilters, SonarGlobalPropertiesFilter sonarGlobalPropertiesFilter, HiddenFilesProjectData hiddenFilesProjectData) { this.analysisWarnings = analysisWarnings; this.scmConfiguration = scmConfiguration; this.inputModuleHierarchy = inputModuleHierarchy; @@ -92,6 +93,7 @@ public class ProjectFilePreprocessor { this.sonarGlobalPropertiesFilter = sonarGlobalPropertiesFilter; this.ignoreCommand = loadIgnoreCommand(); this.useScmExclusion = ignoreCommand != null; + this.hiddenFilesProjectData = hiddenFilesProjectData; } public void execute() { @@ -109,7 +111,7 @@ public class ProjectFilePreprocessor { int totalLanguagesDetected = languageDetection.getDetectedLanguages().size(); - progressReport.stop(String.format("%s detected in %s", pluralizeWithCount("language", totalLanguagesDetected), + progressReport.stopAndLogTotalTime(String.format("%s detected in %s", pluralizeWithCount("language", totalLanguagesDetected), pluralizeWithCount("preprocessed file", totalFilesPreprocessed))); int excludedFileByPatternCount = exclusionCounter.getByPatternsCount(); @@ -138,27 +140,31 @@ public class ProjectFilePreprocessor { // Default to index basedir when no sources provided List<Path> mainSourceDirsOrFiles = module.getSourceDirsOrFiles() .orElseGet(() -> hasChildModules || hasTests ? emptyList() : singletonList(module.getBaseDir().toAbsolutePath())); - List<Path> processedSources = processModuleSources(module, moduleExclusionFilters, mainSourceDirsOrFiles, InputFile.Type.MAIN, + List<Path> processedSources = processModuleSources(module, moduleConfig, moduleExclusionFilters, mainSourceDirsOrFiles, InputFile.Type.MAIN, exclusionCounter); mainSourcesByModule.put(module, processedSources); totalFilesPreprocessed += processedSources.size(); module.getTestDirsOrFiles().ifPresent(tests -> { - List<Path> processedTestSources = processModuleSources(module, moduleExclusionFilters, tests, InputFile.Type.TEST, exclusionCounter); + List<Path> processedTestSources = processModuleSources(module, moduleConfig, moduleExclusionFilters, tests, InputFile.Type.TEST, exclusionCounter); testSourcesByModule.put(module, processedTestSources); totalFilesPreprocessed += processedTestSources.size(); }); } - private List<Path> processModuleSources(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, List<Path> sources, + private List<Path> processModuleSources(DefaultInputModule module, ModuleConfiguration moduleConfiguration, ModuleExclusionFilters moduleExclusionFilters, List<Path> sources, InputFile.Type type, ExclusionCounter exclusionCounter) { List<Path> processedFiles = new ArrayList<>(); try { for (Path dirOrFile : sources) { if (dirOrFile.toFile().isDirectory()) { - processedFiles.addAll(processDirectory(module, moduleExclusionFilters, dirOrFile, type, exclusionCounter)); + processedFiles.addAll(processDirectory(module, moduleConfiguration, moduleExclusionFilters, dirOrFile, type, exclusionCounter)); } else { filePreprocessor.processFile(module, moduleExclusionFilters, dirOrFile, type, exclusionCounter, ignoreCommand) - .ifPresent(processedFiles::add); + .ifPresentOrElse( + processedFiles::add, + // If the file is not processed, we don't need to save visibility data and can remove it + () -> hiddenFilesProjectData.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(dirOrFile, module) + ); } } } catch (IOException e) { @@ -167,12 +173,17 @@ public class ProjectFilePreprocessor { return processedFiles; } - private List<Path> processDirectory(DefaultInputModule module, ModuleExclusionFilters moduleExclusionFilters, Path path, + private List<Path> processDirectory(DefaultInputModule module, ModuleConfiguration moduleConfiguration, ModuleExclusionFilters moduleExclusionFilters, Path path, InputFile.Type type, ExclusionCounter exclusionCounter) throws IOException { List<Path> processedFiles = new ArrayList<>(); Files.walkFileTree(path.normalize(), Collections.singleton(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, - new DirectoryFileVisitor(file -> filePreprocessor.processFile(module, moduleExclusionFilters, file, type, exclusionCounter, - ignoreCommand).ifPresent(processedFiles::add), module, moduleExclusionFilters, inputModuleHierarchy, type)); + new DirectoryFileVisitor(file -> filePreprocessor + .processFile(module, moduleExclusionFilters, file, type, exclusionCounter, ignoreCommand) + .ifPresentOrElse( + processedFiles::add, + // If the file is not processed, we don't need to save visibility data and can remove it + () -> hiddenFilesProjectData.getIsMarkedAsHiddenFileAndRemoveVisibilityInformation(file, module)), + module, moduleConfiguration, moduleExclusionFilters, inputModuleHierarchy, type, hiddenFilesProjectData)); return processedFiles; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/AbstractSensorWrapper.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/AbstractSensorWrapper.java index a08380cf9d8..10d75a4b3c5 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/AbstractSensorWrapper.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/AbstractSensorWrapper.java @@ -19,11 +19,11 @@ */ package org.sonar.scanner.sensor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.sonar.api.batch.sensor.SensorContext; import org.sonar.api.batch.sensor.internal.DefaultSensorDescriptor; import org.sonar.api.scanner.sensor.ProjectSensor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.sonar.scanner.scan.branch.BranchConfiguration; import org.sonar.scanner.scan.branch.BranchType; import org.sonar.scanner.scan.filesystem.MutableFileSystem; @@ -60,7 +60,12 @@ public abstract class AbstractSensorWrapper<G extends ProjectSensor> { if (sensorIsRestricted) { LOGGER.info("Sensor {} is restricted to changed files only", descriptor.name()); } + boolean allowHiddenFileAnalysis = descriptor.isProcessesHiddenFiles(); + if (allowHiddenFileAnalysis) { + LOGGER.debug("Sensor {} is allowed to analyze hidden files", descriptor.name()); + } fileSystem.setRestrictToChangedFiles(sensorIsRestricted); + fileSystem.setAllowHiddenFileAnalysis(allowHiddenFileAnalysis); wrappedSensor.execute(context); } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java index 5f28e7e283e..01b6c0c11cd 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ModuleSensorContext.java @@ -28,7 +28,6 @@ import org.sonar.api.batch.rule.ActiveRules; import org.sonar.api.batch.sensor.cache.ReadCache; import org.sonar.api.batch.sensor.cache.WriteCache; import org.sonar.api.config.Configuration; -import org.sonar.api.config.Settings; import org.sonar.scanner.bootstrap.ScannerPluginRepository; import org.sonar.scanner.cache.AnalysisCacheEnabled; import org.sonar.scanner.scan.branch.BranchConfiguration; @@ -38,11 +37,11 @@ public class ModuleSensorContext extends ProjectSensorContext { private final InputModule module; - public ModuleSensorContext(DefaultInputProject project, InputModule module, Configuration config, Settings mutableModuleSettings, FileSystem fs, ActiveRules activeRules, + public ModuleSensorContext(DefaultInputProject project, InputModule module, Configuration config, FileSystem fs, ActiveRules activeRules, DefaultSensorStorage sensorStorage, SonarRuntime sonarRuntime, BranchConfiguration branchConfiguration, WriteCache writeCache, ReadCache readCache, AnalysisCacheEnabled analysisCacheEnabled, UnchangedFilesHandler unchangedFilesHandler, ExecutingSensorContext executingSensorContext, ScannerPluginRepository pluginRepository) { - super(project, config, mutableModuleSettings, fs, activeRules, sensorStorage, sonarRuntime, branchConfiguration, writeCache, readCache, analysisCacheEnabled, + super(project, config, fs, activeRules, sensorStorage, sonarRuntime, branchConfiguration, writeCache, readCache, analysisCacheEnabled, unchangedFilesHandler, executingSensorContext, pluginRepository); this.module = module; } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java index 6fb38fa4563..54c86750eaf 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/sensor/ProjectSensorContext.java @@ -66,7 +66,6 @@ public class ProjectSensorContext implements SensorContext { static final NoOpNewAnalysisError NO_OP_NEW_ANALYSIS_ERROR = new NoOpNewAnalysisError(); - private final Settings mutableSettings; private final FileSystem fs; private final ActiveRules activeRules; private final DefaultSensorStorage sensorStorage; @@ -81,15 +80,14 @@ public class ProjectSensorContext implements SensorContext { private final ExecutingSensorContext executingSensorContext; private final ScannerPluginRepository pluginRepo; - public ProjectSensorContext(DefaultInputProject project, Configuration config, Settings mutableSettings, FileSystem fs, - ActiveRules activeRules, - DefaultSensorStorage sensorStorage, SonarRuntime sonarRuntime, BranchConfiguration branchConfiguration, - WriteCache writeCache, ReadCache readCache, - AnalysisCacheEnabled analysisCacheEnabled, UnchangedFilesHandler unchangedFilesHandler, - ExecutingSensorContext executingSensorContext, ScannerPluginRepository pluginRepo) { + public ProjectSensorContext(DefaultInputProject project, Configuration config, FileSystem fs, + ActiveRules activeRules, + DefaultSensorStorage sensorStorage, SonarRuntime sonarRuntime, BranchConfiguration branchConfiguration, + WriteCache writeCache, ReadCache readCache, + AnalysisCacheEnabled analysisCacheEnabled, UnchangedFilesHandler unchangedFilesHandler, + ExecutingSensorContext executingSensorContext, ScannerPluginRepository pluginRepo) { this.project = project; this.config = config; - this.mutableSettings = mutableSettings; this.fs = fs; this.activeRules = activeRules; this.sensorStorage = sensorStorage; @@ -105,7 +103,7 @@ public class ProjectSensorContext implements SensorContext { @Override public Settings settings() { - return mutableSettings; + throw new UnsupportedOperationException("This method is not supported anymore"); } @Override diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java index 0742740bba6..a481f4a54f4 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/CompositeBlameCommand.java @@ -22,7 +22,9 @@ package org.sonar.scm.git; import java.io.File; import java.io.IOException; import java.nio.file.Files; +import java.time.Instant; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -32,6 +34,7 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.RawTextComparator; @@ -238,7 +241,7 @@ public class CompositeBlameCommand extends BlameCommand { break; } linesList.add(new BlameLine() - .date(fileBlame.getCommitDates()[i]) + .date(toDate(fileBlame.getCommitDates()[i])) .revision(fileBlame.getCommitHashes()[i]) .author(fileBlame.getAuthorEmails()[i])); } @@ -251,4 +254,8 @@ public class CompositeBlameCommand extends BlameCommand { } } + private static @Nullable Date toDate(@Nullable Instant commitDate) { + return commitDate != null ? Date.from(commitDate) : null; + } + } diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/NativeGitBlameCommand.java b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/NativeGitBlameCommand.java index 569999a55ab..8f066727e21 100644 --- a/sonar-scanner-engine/src/main/java/org/sonar/scm/git/NativeGitBlameCommand.java +++ b/sonar-scanner-engine/src/main/java/org/sonar/scm/git/NativeGitBlameCommand.java @@ -25,6 +25,7 @@ import java.time.Instant; import java.util.Date; import java.util.LinkedList; import java.util.List; +import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -62,6 +63,7 @@ public class NativeGitBlameCommand { private final System2 system; private final ProcessWrapperFactory processWrapperFactory; + private final Consumer<String> stderrConsumer = line -> LOG.debug("[stderr] {}", line); private String gitCommand; @Autowired @@ -85,7 +87,7 @@ public class NativeGitBlameCommand { try { this.gitCommand = locateDefaultGit(); MutableString stdOut = new MutableString(); - this.processWrapperFactory.create(null, l -> stdOut.string = l, gitCommand, "--version").execute(); + this.processWrapperFactory.create(null, l -> stdOut.string = l, stderrConsumer, gitCommand, "--version").execute(); return stdOut.string != null && stdOut.string.startsWith("git version") && isCompatibleGitVersion(stdOut.string); } catch (Exception e) { LOG.debug("Failed to find git native client", e); @@ -109,7 +111,7 @@ public class NativeGitBlameCommand { // To avoid it we use where.exe to find git binary only in PATH. LOG.debug("Looking for git command in the PATH using where.exe (Windows)"); List<String> whereCommandResult = new LinkedList<>(); - this.processWrapperFactory.create(null, whereCommandResult::add, "C:\\Windows\\System32\\where.exe", "$PATH:git.exe") + this.processWrapperFactory.create(null, whereCommandResult::add, stderrConsumer, "C:\\Windows\\System32\\where.exe", "$PATH:git.exe") .execute(); if (!whereCommandResult.isEmpty()) { @@ -125,6 +127,7 @@ public class NativeGitBlameCommand { var processWrapper = this.processWrapperFactory.create( baseDir, outputProcessor::process, + stderrConsumer, gitCommand, GIT_DIR_FLAG, String.format(GIT_DIR_ARGUMENT, baseDir), GIT_DIR_FORCE_FLAG, baseDir.toString(), BLAME_COMMAND, |