internal/platform/effectiveconfig/config.go (255 lines of code) (raw):

/* * Copyright 2021-2024 JetBrains s.r.o. * * 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 * * https://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 effectiveconfig import ( "errors" "fmt" "os" "path/filepath" "github.com/JetBrains/qodana-cli/internal/platform/msg" "github.com/JetBrains/qodana-cli/internal/platform/qdyaml" "github.com/JetBrains/qodana-cli/internal/platform/strutil" "github.com/JetBrains/qodana-cli/internal/platform/utils" "github.com/JetBrains/qodana-cli/internal/tooling" log "github.com/sirupsen/logrus" ) // Files – effective configuration files, constructed by calling config-loader-cli.jar, // all paths are absolute // + also profile files are stored in config dir type Files struct { ConfigDir string EffectiveQodanaYamlPath string LocalQodanaYamlPath string QodanaConfigJsonPath string } func CreateEffectiveConfigFiles( localQodanaYamlFullPath string, globalConfigurationsDir string, globalConfigId string, jrePath string, effectiveConfigDir string, logDir string, ) (Files, error) { if globalConfigId != "" && globalConfigurationsDir == "" { return Files{}, fmt.Errorf( "global configuration id %s is defined without global cofigurations directory", globalConfigId, ) } if globalConfigurationsDir != "" && globalConfigId == "" { return Files{}, fmt.Errorf( "global configurations directory %s is defined without global configuration id", globalConfigurationsDir, ) } globalConfigurationsFile := "" if globalConfigurationsDir != "" { globalConfigurationsYamlFilename := "qodana-global-configurations.yaml" globalConfigurationsFile = filepath.Join(globalConfigurationsDir, globalConfigurationsYamlFilename) if !isFileExists(globalConfigurationsFile) { msg.ErrorMessage( "%s file not found in global configuration directory %s", globalConfigurationsYamlFilename, globalConfigurationsDir, ) return Files{}, fmt.Errorf("%s doesn't exist", globalConfigurationsFile) } } configLoaderTempDir, cleanup, err := utils.CreateTempDir("config-loader-cli") if err != nil { return Files{}, err } defer cleanup() configLoaderCliJar, err := createConfigLoaderCliJar(configLoaderTempDir) if err != nil { return Files{}, err } defer func(name string) { err := os.Remove(name) if err != nil { log.Warnf("Failed to delete config-loader-cli.jar: %v", err) } }(configLoaderCliJar) args, err := configurationLoaderCliArgs( jrePath, configLoaderCliJar, localQodanaYamlFullPath, globalConfigurationsFile, globalConfigId, effectiveConfigDir, ) if err != nil { return Files{}, err } log.Debugf("Creating effective configuration in '%s' directory, args: %v", effectiveConfigDir, args) if _, _, res, err := utils.LaunchAndLog(logDir, "config-loader-cli", args...); res > 0 || err != nil { if err == nil { err = errors.New("failed to create effective configuration") } else { err = fmt.Errorf("failed to create effective configuration: %v", err) } msg.ErrorMessage("Failed to create effective configuration.") return Files{}, err } effectiveQodanaYamlData, err := getEffectiveQodanaYamlData(effectiveConfigDir) if err != nil { return Files{}, err } err = verifyEffectiveQodanaYamlIdeAndLinterMatchLocal(effectiveQodanaYamlData, localQodanaYamlFullPath) if err != nil { return Files{}, err } msg.SuccessMessage("Loaded Qodana Configuration") return effectiveQodanaYamlData, nil } func createConfigLoaderCliJar(dir string) (string, error) { configLoaderCliJarPath := filepath.Join(dir, "config-loader-cli.jar") if isFileExists(configLoaderCliJarPath) { err := os.Remove(configLoaderCliJarPath) if err != nil { return "", fmt.Errorf("failed to delete existing config-loader-cli.jar: %v", err) } } err := os.MkdirAll(filepath.Dir(configLoaderCliJarPath), 0755) if err != nil { return "", fmt.Errorf("failed to create directory for config-loader-cli.jar: %v", err) } log.Debugf("creating config-loader-cli.jar at '%s'", configLoaderCliJarPath) err = os.WriteFile(configLoaderCliJarPath, tooling.ConfigLoaderCli, 0644) if err != nil { return "", fmt.Errorf("failed to write config-loader-cli.jar content to %s: %v", configLoaderCliJarPath, err) } return configLoaderCliJarPath, nil } func configurationLoaderCliArgs( jrePath string, configLoaderCliJarPath string, localQodanaYamlPath string, globalConfigurationsFile string, globalConfigId string, effectiveConfigDir string, ) ([]string, error) { if jrePath == "" { return nil, fmt.Errorf( "JRE not found. Required for effective configuration creation. " + "See requirements in our documentation: https://www.jetbrains.com/help/qodana/deploy-qodana.html", ) } if configLoaderCliJarPath == "" { return nil, fmt.Errorf("config-loader-cli.jar not found. Required for effective configuration creation") } var err error args := []string{ strutil.QuoteIfSpace(strutil.QuoteForWindows(jrePath)), "-jar", strutil.QuoteForWindows(configLoaderCliJarPath), } effectiveConfigDirAbs, err := filepath.Abs(effectiveConfigDir) if err != nil { err := fmt.Errorf( "failed to compute absolute path of effective configuration directory %s: %v", effectiveConfigDir, err, ) return nil, err } args = append(args, "--effective-config-out-dir", strutil.QuoteForWindows(effectiveConfigDirAbs)) if localQodanaYamlPath != "" { localQodanaYamlPathAbs, err := filepath.Abs(localQodanaYamlPath) if err != nil { err := fmt.Errorf( "failed to compute absolute path of local qodana.yaml file %s: %v", localQodanaYamlPath, err, ) return nil, err } args = append( args, "--local-qodana-yaml", strutil.QuoteIfSpace(strutil.QuoteForWindows(localQodanaYamlPathAbs)), ) } if globalConfigurationsFile != "" { globalConfigurationsFileAbs, err := filepath.Abs(globalConfigurationsFile) if err != nil { err := fmt.Errorf( "failed to compute absolute path of global configurations file %s: %v", globalConfigurationsFile, err, ) return nil, err } args = append( args, "--global-configs-file", strutil.QuoteIfSpace(strutil.QuoteForWindows(globalConfigurationsFileAbs)), ) } if globalConfigId != "" { args = append(args, "--global-config-id", strutil.QuoteIfSpace(strutil.QuoteForWindows(globalConfigId))) } return args, nil } func getEffectiveQodanaYamlData(effectiveConfigDir string) (Files, error) { effectiveQodanaYamlPath := filepath.Join(effectiveConfigDir, "effective.qodana.yaml") if !isFileExists(effectiveQodanaYamlPath) { effectiveQodanaYamlPath = "" } localQodanaYamlPath := filepath.Join(effectiveConfigDir, "qodana.yaml") if !isFileExists(localQodanaYamlPath) { localQodanaYamlPath = "" } qodanaConfigJsonPath := filepath.Join(effectiveConfigDir, "qodana-config.json") if !isFileExists(qodanaConfigJsonPath) { qodanaConfigJsonPath = "" } if effectiveQodanaYamlPath != "" && qodanaConfigJsonPath == "" { return Files{}, errors.New("effective.qodana.yaml file doesn't have a qodana-config.json file") } if localQodanaYamlPath != "" && effectiveQodanaYamlPath == "" { return Files{}, errors.New("local qodana.yaml file doesn't have an effective.qodana.yaml file") } return Files{ ConfigDir: effectiveConfigDir, EffectiveQodanaYamlPath: effectiveQodanaYamlPath, LocalQodanaYamlPath: localQodanaYamlPath, QodanaConfigJsonPath: qodanaConfigJsonPath, }, nil } //goland:noinspection GoRedundantElseInIf func isFileExists(path string) bool { if _, err := os.Stat(path); err == nil { return true } else if os.IsNotExist(err) { return false } else { log.Fatalf("Failed to verify existence of file %s: %s", path, err) } return false } func verifyEffectiveQodanaYamlIdeAndLinterMatchLocal( effectiveQodanaYamlData Files, localQodanaYamlPathFromRoot string, ) error { effectiveYaml := qdyaml.LoadQodanaYamlByFullPath(effectiveQodanaYamlData.EffectiveQodanaYamlPath) effectiveLinter := effectiveYaml.Linter effectiveIde := effectiveYaml.Ide if effectiveLinter == "" && effectiveIde == "" { return nil } isLocalQodanaYamlPresent := effectiveQodanaYamlData.LocalQodanaYamlPath != "" if isLocalQodanaYamlPresent { localQodanaYaml := qdyaml.LoadQodanaYamlByFullPath(effectiveQodanaYamlData.LocalQodanaYamlPath) failedToCreateEffectiveConfigurationMessage := "Failed to create effective configuration" topMessageTemplate := "'%s: %s' is specified in one of files provided by 'imports' from " + localQodanaYamlPathFromRoot + " '%s' is required in root qodana.yaml" bottomMessageTemplate := "Add `%s: %s` to " + localQodanaYamlPathFromRoot if effectiveIde != localQodanaYaml.Ide { msg.ErrorMessage(failedToCreateEffectiveConfigurationMessage) msg.ErrorMessage(topMessageTemplate, "ide", effectiveIde, "ide") msg.ErrorMessage(bottomMessageTemplate, "ide", effectiveIde) return errors.New("effective.qodana.yaml `ide` doesn't match root qodana.yaml `ide`") } //goland:noinspection GoDfaConstantCondition if effectiveLinter != localQodanaYaml.Linter { msg.ErrorMessage(failedToCreateEffectiveConfigurationMessage) msg.ErrorMessage(topMessageTemplate, "linter", effectiveLinter, "linter") msg.ErrorMessage(bottomMessageTemplate, "linter", effectiveLinter) return errors.New("effective.qodana.yaml `linter` doesn't match root qodana.yaml `linter`") } } return nil }