github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/commands/mvn/mvn.go (about) 1 package mvn 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "os/exec" 9 "path" 10 "path/filepath" 11 "strings" 12 13 "github.com/jfrog/jfrog-cli-core/utils/coreutils" 14 15 commandsutils "github.com/jfrog/jfrog-cli-core/artifactory/commands/utils" 16 "github.com/jfrog/jfrog-cli-core/artifactory/utils" 17 "github.com/jfrog/jfrog-cli-core/utils/config" 18 "github.com/jfrog/jfrog-client-go/utils/errorutils" 19 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 20 "github.com/jfrog/jfrog-client-go/utils/log" 21 "github.com/spf13/viper" 22 ) 23 24 const mavenExtractorDependencyVersion = "2.28.6" 25 26 // Deprecated. This version is the latest published in JCenter. 27 const mavenExtractorDependencyJCenterVersion = "2.23.0" 28 const classworldsConfFileName = "classworlds.conf" 29 const MavenHome = "M2_HOME" 30 31 type MvnCommand struct { 32 goals []string 33 configPath string 34 insecureTls bool 35 configuration *utils.BuildConfiguration 36 serverDetails *config.ServerDetails 37 threads int 38 detailedSummary bool 39 result *commandsutils.Result 40 } 41 42 func NewMvnCommand() *MvnCommand { 43 return &MvnCommand{} 44 } 45 46 func (mc *MvnCommand) SetServerDetails(serverDetails *config.ServerDetails) *MvnCommand { 47 mc.serverDetails = serverDetails 48 return mc 49 } 50 51 func (mc *MvnCommand) SetConfiguration(configuration *utils.BuildConfiguration) *MvnCommand { 52 mc.configuration = configuration 53 return mc 54 } 55 56 func (mc *MvnCommand) SetConfigPath(configPath string) *MvnCommand { 57 mc.configPath = configPath 58 return mc 59 } 60 61 func (mc *MvnCommand) SetGoals(goals []string) *MvnCommand { 62 mc.goals = goals 63 return mc 64 } 65 66 func (mc *MvnCommand) SetThreads(threads int) *MvnCommand { 67 mc.threads = threads 68 return mc 69 } 70 71 func (mc *MvnCommand) SetInsecureTls(insecureTls bool) *MvnCommand { 72 mc.insecureTls = insecureTls 73 return mc 74 } 75 76 func (mc *MvnCommand) SetDetailedSummary(detailedSummary bool) *MvnCommand { 77 mc.detailedSummary = detailedSummary 78 return mc 79 } 80 81 func (mc *MvnCommand) IsDetailedSummary() bool { 82 return mc.detailedSummary 83 } 84 85 func (mc *MvnCommand) Result() *commandsutils.Result { 86 return mc.result 87 } 88 89 func (mc *MvnCommand) SetResult(result *commandsutils.Result) *MvnCommand { 90 mc.result = result 91 return mc 92 } 93 94 func (mc *MvnCommand) Run() error { 95 log.Info("Running Mvn...") 96 97 dependenciesPath, err := downloadDependencies() 98 if err != nil { 99 return err 100 } 101 102 mvnRunConfig, err := mc.createMvnRunConfig(dependenciesPath) 103 if err != nil { 104 return err 105 } 106 107 defer os.Remove(mvnRunConfig.buildInfoProperties) 108 err = mvnRunConfig.runCmd() 109 if err != nil { 110 return err 111 } 112 if mc.IsDetailedSummary() { 113 return mc.unmarshalDeployableArtifacts(mvnRunConfig.deployableArtifactsFilePath) 114 } 115 return nil 116 } 117 118 // Returns the ServerDetails. The information returns from the config file provided. 119 func (mc *MvnCommand) ServerDetails() (*config.ServerDetails, error) { 120 // Get the serverDetails from the config file. 121 var err error 122 if mc.serverDetails == nil { 123 vConfig, err := utils.ReadConfigFile(mc.configPath, utils.YAML) 124 if err != nil { 125 return nil, err 126 } 127 mc.serverDetails, err = utils.GetServerDetails(vConfig) 128 } 129 return mc.serverDetails, err 130 } 131 132 func (mc *MvnCommand) CommandName() string { 133 return "rt_maven" 134 } 135 136 func validateMavenInstallation() error { 137 log.Debug("Checking prerequisites.") 138 mavenHome := os.Getenv(MavenHome) 139 if mavenHome == "" { 140 return errorutils.CheckError(errors.New(MavenHome + " environment variable is not set")) 141 } 142 return nil 143 } 144 145 func downloadDependencies() (string, error) { 146 dependenciesPath, err := config.GetJfrogDependenciesPath() 147 if err != nil { 148 return "", err 149 } 150 extractorVersion := utils.GetExtractorVersion(mavenExtractorDependencyVersion, mavenExtractorDependencyJCenterVersion) 151 dependenciesPath = filepath.Join(dependenciesPath, "maven", extractorVersion) 152 153 filename := fmt.Sprintf("build-info-extractor-maven3-%s-uber.jar", extractorVersion) 154 filePath := fmt.Sprintf("org/jfrog/buildinfo/build-info-extractor-maven3/%s", extractorVersion) 155 downloadPath := path.Join(filePath, filename) 156 157 err = utils.DownloadExtractorIfNeeded(downloadPath, filepath.Join(dependenciesPath, filename)) 158 if err != nil { 159 return "", err 160 } 161 162 err = createClassworldsConfig(dependenciesPath) 163 return dependenciesPath, err 164 } 165 166 func createClassworldsConfig(dependenciesPath string) error { 167 classworldsPath := filepath.Join(dependenciesPath, classworldsConfFileName) 168 169 if fileutils.IsPathExists(classworldsPath, false) { 170 return nil 171 } 172 return errorutils.CheckError(os.WriteFile(classworldsPath, []byte(utils.ClassworldsConf), 0644)) 173 } 174 175 func (mc *MvnCommand) createMvnRunConfig(dependenciesPath string) (*mvnRunConfig, error) { 176 var err error 177 var javaExecPath string 178 179 javaHome := os.Getenv("JAVA_HOME") 180 if javaHome != "" { 181 javaExecPath = filepath.Join(javaHome, "bin", "java") 182 } else { 183 javaExecPath, err = exec.LookPath("java") 184 if err != nil { 185 return nil, errorutils.CheckError(err) 186 } 187 } 188 189 mavenHome, err := getMavenHome() 190 if err != nil { 191 return nil, err 192 } 193 plexusClassworlds, err := filepath.Glob(filepath.Join(mavenHome, "boot", "plexus-classworlds*.jar")) 194 if err != nil { 195 return nil, errorutils.CheckError(err) 196 } 197 198 mavenOpts := os.Getenv("MAVEN_OPTS") 199 200 if len(plexusClassworlds) != 1 { 201 return nil, errorutils.CheckError(errors.New("couldn't find plexus-classworlds-x.x.x.jar in Maven installation path, please check M2_HOME environment variable")) 202 } 203 204 var currentWorkdir string 205 currentWorkdir, err = os.Getwd() 206 if err != nil { 207 return nil, errorutils.CheckError(err) 208 } 209 210 var vConfig *viper.Viper 211 vConfig, err = utils.ReadConfigFile(mc.configPath, utils.YAML) 212 if err != nil { 213 return nil, err 214 } 215 216 if len(mc.configuration.BuildName) > 0 && len(mc.configuration.BuildNumber) > 0 { 217 vConfig.Set(utils.BuildName, mc.configuration.BuildName) 218 vConfig.Set(utils.BuildNumber, mc.configuration.BuildNumber) 219 vConfig.Set(utils.BuildProject, mc.configuration.Project) 220 err = utils.SaveBuildGeneralDetails(mc.configuration.BuildName, mc.configuration.BuildNumber, mc.configuration.Project) 221 if err != nil { 222 return nil, err 223 } 224 } 225 vConfig.Set(utils.InsecureTls, mc.insecureTls) 226 227 if mc.threads > 0 { 228 vConfig.Set(utils.ForkCount, mc.threads) 229 } 230 231 if !vConfig.IsSet("deployer") { 232 setEmptyDeployer(vConfig) 233 } 234 235 buildInfoProperties, err := utils.CreateBuildInfoPropertiesFile(mc.configuration.BuildName, mc.configuration.BuildNumber, mc.configuration.Project, mc.IsDetailedSummary(), vConfig, utils.Maven) 236 if err != nil { 237 return nil, err 238 } 239 240 return &mvnRunConfig{ 241 java: javaExecPath, 242 pluginDependencies: dependenciesPath, 243 plexusClassworlds: plexusClassworlds[0], 244 cleassworldsConfig: filepath.Join(dependenciesPath, classworldsConfFileName), 245 mavenHome: mavenHome, 246 workspace: currentWorkdir, 247 goals: mc.goals, 248 buildInfoProperties: buildInfoProperties, 249 artifactoryResolutionEnabled: vConfig.IsSet("resolver"), 250 generatedBuildInfoPath: vConfig.GetString(utils.GeneratedBuildInfo), 251 mavenOpts: mavenOpts, 252 deployableArtifactsFilePath: vConfig.GetString(utils.DeployableArtifacts), 253 }, nil 254 } 255 256 func (mc *MvnCommand) unmarshalDeployableArtifacts(filesPath string) error { 257 result, err := commandsutils.UnmarshalDeployableArtifacts(filesPath, mc.configPath) 258 if err != nil { 259 return err 260 } 261 mc.SetResult(result) 262 return nil 263 } 264 265 func setEmptyDeployer(vConfig *viper.Viper) { 266 vConfig.Set(utils.DeployerPrefix+utils.DeployArtifacts, "false") 267 vConfig.Set(utils.DeployerPrefix+utils.Url, "http://empty_url") 268 vConfig.Set(utils.DeployerPrefix+utils.ReleaseRepo, "empty_repo") 269 vConfig.Set(utils.DeployerPrefix+utils.SnapshotRepo, "empty_repo") 270 } 271 272 func (config *mvnRunConfig) GetCmd() *exec.Cmd { 273 var cmd []string 274 cmd = append(cmd, config.java) 275 cmd = append(cmd, "-classpath", config.plexusClassworlds) 276 cmd = append(cmd, "-Dmaven.home="+config.mavenHome) 277 cmd = append(cmd, "-DbuildInfoConfig.propertiesFile="+config.buildInfoProperties) 278 if config.artifactoryResolutionEnabled { 279 cmd = append(cmd, "-DbuildInfoConfig.artifactoryResolutionEnabled=true") 280 } 281 cmd = append(cmd, "-Dm3plugin.lib="+config.pluginDependencies) 282 cmd = append(cmd, "-Dclassworlds.conf="+config.cleassworldsConfig) 283 cmd = append(cmd, "-Dmaven.multiModuleProjectDirectory="+config.workspace) 284 if config.mavenOpts != "" { 285 cmd = append(cmd, strings.Split(config.mavenOpts, " ")...) 286 } 287 cmd = append(cmd, "org.codehaus.plexus.classworlds.launcher.Launcher") 288 cmd = append(cmd, config.goals...) 289 return exec.Command(cmd[0], cmd[1:]...) 290 } 291 292 func (config *mvnRunConfig) runCmd() error { 293 command := config.GetCmd() 294 command.Stderr = os.Stderr 295 command.Stdout = os.Stderr 296 return coreutils.ConvertExitCodeError(errorutils.CheckError(command.Run())) 297 } 298 299 type mvnRunConfig struct { 300 java string 301 plexusClassworlds string 302 cleassworldsConfig string 303 mavenHome string 304 pluginDependencies string 305 workspace string 306 pom string 307 goals []string 308 buildInfoProperties string 309 artifactoryResolutionEnabled bool 310 generatedBuildInfoPath string 311 mavenOpts string 312 deployableArtifactsFilePath string 313 } 314 315 func getMavenHome() (string, error) { 316 log.Debug("Checking prerequisites.") 317 mavenHome := os.Getenv(MavenHome) 318 if mavenHome == "" { 319 // The M2_HOME environment variable is not defined. 320 // Since Maven installation can be located in different locations, 321 // Depending on the installation type and the OS (for example: For Mac with brew install: /usr/local/Cellar/maven/{version}/libexec or Ubuntu with debian: /usr/share/maven), 322 // We need to grab the location using the mvn --version command 323 324 // First we will try lo look for 'mvn' in PATH. 325 mvnPath, err := exec.LookPath("mvn") 326 if err != nil || mvnPath == "" { 327 return "", errorutils.CheckError(errors.New(err.Error() + "Hint: The mvn command may not be included in the PATH. Either add it to the path, or set the M2_HOME environment variable value to the maven installation directory, which is the directory which includes the bin and lib directories.")) 328 } 329 log.Debug(MavenHome, " is not defined. Retrieving Maven home using 'mvn --version' command.") 330 cmd := exec.Command("mvn", "--version") 331 var stdout bytes.Buffer 332 cmd.Stdout = &stdout 333 err = errorutils.CheckError(cmd.Run()) 334 if err != nil { 335 return "", err 336 } 337 output := strings.Split(strings.TrimSpace(stdout.String()), "\n") 338 // Finding the relevant "Maven home" line in command response. 339 for _, line := range output { 340 if strings.HasPrefix(line, "Maven home:") { 341 mavenHome = strings.Split(line, " ")[2] 342 if coreutils.IsWindows() { 343 mavenHome = strings.TrimSuffix(mavenHome, "\r") 344 } 345 mavenHome, err = filepath.Abs(mavenHome) 346 break 347 } 348 } 349 if mavenHome == "" { 350 return "", errorutils.CheckError(errors.New("Could not find the location of the maven home directory, by running 'mvn --version' command. The command output is:\n" + stdout.String() + "\nYou also have the option of setting the M2_HOME environment variable value to the maven installation directory, which is the directory which includes the bin and lib directories.")) 351 } 352 } 353 log.Debug("Maven home location: ", mavenHome) 354 return mavenHome, nil 355 }