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  }