2020-02-28 14:09:46 +02:00
package maven
import (
"bytes"
2020-03-13 15:54:49 +02:00
"fmt"
2020-03-02 18:04:49 +02:00
"io"
2020-04-24 10:41:49 +02:00
"net/http"
2020-11-10 18:14:55 +02:00
"os"
2020-06-15 12:46:54 +02:00
"path/filepath"
2020-03-02 18:04:49 +02:00
"strings"
2020-02-28 14:09:46 +02:00
2021-06-01 09:24:36 +02:00
"github.com/SAP/jenkins-library/pkg/command"
piperhttp "github.com/SAP/jenkins-library/pkg/http"
"github.com/SAP/jenkins-library/pkg/piperutils"
2020-02-28 14:09:46 +02:00
"github.com/SAP/jenkins-library/pkg/log"
)
2020-03-13 15:54:49 +02:00
// ExecuteOptions are used by Execute() to construct the Maven command line.
2020-02-28 14:09:46 +02:00
type ExecuteOptions struct {
PomPath string ` json:"pomPath,omitempty" `
ProjectSettingsFile string ` json:"projectSettingsFile,omitempty" `
GlobalSettingsFile string ` json:"globalSettingsFile,omitempty" `
M2Path string ` json:"m2Path,omitempty" `
Goals [ ] string ` json:"goals,omitempty" `
Defines [ ] string ` json:"defines,omitempty" `
Flags [ ] string ` json:"flags,omitempty" `
LogSuccessfulMavenTransfers bool ` json:"logSuccessfulMavenTransfers,omitempty" `
ReturnStdout bool ` json:"returnStdout,omitempty" `
}
2020-06-11 14:02:54 +02:00
// EvaluateOptions are used by Evaluate() to construct the Maven command line.
// In contrast to ExecuteOptions, fewer settings are required for Evaluate and thus a separate type is needed.
type EvaluateOptions struct {
2020-06-17 19:08:43 +02:00
PomPath string ` json:"pomPath,omitempty" `
ProjectSettingsFile string ` json:"projectSettingsFile,omitempty" `
GlobalSettingsFile string ` json:"globalSettingsFile,omitempty" `
M2Path string ` json:"m2Path,omitempty" `
Defines [ ] string ` json:"defines,omitempty" `
2020-06-11 14:02:54 +02:00
}
2020-11-10 18:14:55 +02:00
type Utils interface {
2020-02-28 14:09:46 +02:00
Stdout ( out io . Writer )
Stderr ( err io . Writer )
RunExecutable ( e string , p ... string ) error
2020-04-24 10:41:49 +02:00
DownloadFile ( url , filename string , header http . Header , cookies [ ] * http . Cookie ) error
2020-11-10 18:14:55 +02:00
Glob ( pattern string ) ( matches [ ] string , err error )
FileExists ( filename string ) ( bool , error )
Copy ( src , dest string ) ( int64 , error )
MkdirAll ( path string , perm os . FileMode ) error
2021-06-01 09:24:36 +02:00
FileWrite ( path string , content [ ] byte , perm os . FileMode ) error
FileRead ( path string ) ( [ ] byte , error )
2020-04-24 10:41:49 +02:00
}
type utilsBundle struct {
2020-11-10 18:14:55 +02:00
* command . Command
2020-06-11 14:02:54 +02:00
* piperutils . Files
2020-11-10 18:14:55 +02:00
* piperhttp . Client
2020-04-24 10:41:49 +02:00
}
2020-11-10 18:14:55 +02:00
func NewUtilsBundle ( ) Utils {
utils := utilsBundle {
Command : & command . Command { } ,
Files : & piperutils . Files { } ,
Client : & piperhttp . Client { } ,
2020-04-24 10:41:49 +02:00
}
2020-11-10 18:14:55 +02:00
utils . Stdout ( log . Writer ( ) )
utils . Stderr ( log . Writer ( ) )
return & utils
2020-04-24 10:41:49 +02:00
}
2020-02-28 14:09:46 +02:00
const mavenExecutable = "mvn"
2020-03-13 15:54:49 +02:00
// Execute constructs a mvn command line from the given options, and uses the provided
// mavenExecRunner to execute it.
2020-11-10 18:14:55 +02:00
func Execute ( options * ExecuteOptions , utils Utils ) ( string , error ) {
2020-02-28 14:09:46 +02:00
stdOutBuf , stdOut := evaluateStdOut ( options )
2020-11-10 18:14:55 +02:00
utils . Stdout ( stdOut )
utils . Stderr ( log . Writer ( ) )
2020-02-28 14:09:46 +02:00
2020-11-10 18:14:55 +02:00
parameters , err := getParametersFromOptions ( options , utils )
2020-03-17 09:33:35 +02:00
if err != nil {
return "" , fmt . Errorf ( "failed to construct parameters from options: %w" , err )
}
2020-02-28 14:09:46 +02:00
2020-11-10 18:14:55 +02:00
err = utils . RunExecutable ( mavenExecutable , parameters ... )
2020-02-28 14:09:46 +02:00
if err != nil {
2020-10-05 17:46:44 +02:00
log . SetErrorCategory ( log . ErrorBuild )
2020-03-17 09:33:35 +02:00
commandLine := append ( [ ] string { mavenExecutable } , parameters ... )
return "" , fmt . Errorf ( "failed to run executable, command: '%s', error: %w" , commandLine , err )
2020-02-28 14:09:46 +02:00
}
if stdOutBuf == nil {
return "" , nil
}
return string ( stdOutBuf . Bytes ( ) ) , nil
}
2020-03-13 15:54:49 +02:00
// Evaluate constructs ExecuteOptions for using the maven-help-plugin's 'evaluate' goal to
// evaluate a given expression from a pom file. This allows to retrieve the value of - for
// example - 'project.version' from a pom file exactly as Maven itself evaluates it.
2020-11-10 18:14:55 +02:00
func Evaluate ( options * EvaluateOptions , expression string , utils Utils ) ( string , error ) {
2020-06-17 19:08:43 +02:00
defines := [ ] string { "-Dexpression=" + expression , "-DforceStdout" , "-q" }
defines = append ( defines , options . Defines ... )
2020-06-11 14:02:54 +02:00
executeOptions := ExecuteOptions {
PomPath : options . PomPath ,
M2Path : options . M2Path ,
ProjectSettingsFile : options . ProjectSettingsFile ,
GlobalSettingsFile : options . GlobalSettingsFile ,
Goals : [ ] string { "org.apache.maven.plugins:maven-help-plugin:3.1.0:evaluate" } ,
2020-06-17 19:08:43 +02:00
Defines : defines ,
2020-06-11 14:02:54 +02:00
ReturnStdout : true ,
2020-03-13 15:54:49 +02:00
}
2020-11-10 18:14:55 +02:00
value , err := Execute ( & executeOptions , utils )
2020-03-13 15:54:49 +02:00
if err != nil {
return "" , err
}
if strings . HasPrefix ( value , "null object or invalid expression" ) {
2020-06-11 14:02:54 +02:00
return "" , fmt . Errorf ( "expression '%s' in file '%s' could not be resolved" , expression , options . PomPath )
2020-03-13 15:54:49 +02:00
}
return value , nil
}
2020-06-15 12:46:54 +02:00
// InstallFile installs a maven artifact and its pom into the local maven repository.
// If "file" is empty, only the pom is installed. "pomFile" must not be empty.
2020-11-10 18:14:55 +02:00
func InstallFile ( file , pomFile string , options * EvaluateOptions , utils Utils ) error {
2020-06-15 12:46:54 +02:00
if len ( pomFile ) == 0 {
return fmt . Errorf ( "pomFile can't be empty" )
}
var defines [ ] string
if len ( file ) > 0 {
defines = append ( defines , "-Dfile=" + file )
if strings . Contains ( file , ".jar" ) {
defines = append ( defines , "-Dpackaging=jar" )
}
if strings . Contains ( file , "-classes" ) {
defines = append ( defines , "-Dclassifier=classes" )
}
} else {
defines = append ( defines , "-Dfile=" + pomFile )
}
defines = append ( defines , "-DpomFile=" + pomFile )
mavenOptionsInstall := ExecuteOptions {
2020-10-30 11:04:38 +02:00
Goals : [ ] string { "install:install-file" } ,
Defines : defines ,
M2Path : options . M2Path ,
ProjectSettingsFile : options . ProjectSettingsFile ,
GlobalSettingsFile : options . GlobalSettingsFile ,
2020-06-15 12:46:54 +02:00
}
2020-11-10 18:14:55 +02:00
_ , err := Execute ( & mavenOptionsInstall , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return fmt . Errorf ( "failed to install maven artifacts: %w" , err )
}
return nil
}
// InstallMavenArtifacts finds maven modules (identified by pom.xml files) and installs the artifacts into the local maven repository.
2020-11-10 18:14:55 +02:00
func InstallMavenArtifacts ( options * EvaluateOptions , utils Utils ) error {
return doInstallMavenArtifacts ( options , utils )
2020-06-15 12:46:54 +02:00
}
2020-11-10 18:14:55 +02:00
func doInstallMavenArtifacts ( options * EvaluateOptions , utils Utils ) error {
err := flattenPom ( options , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
pomFiles , err := utils . Glob ( filepath . Join ( "**" , "pom.xml" ) )
if err != nil {
return err
}
2020-06-16 09:48:47 +02:00
// Ensure m2 path is an absolute path, even if it is given relative
// This is important to avoid getting multiple m2 directories in a maven multimodule project
if options . M2Path != "" {
options . M2Path , err = filepath . Abs ( options . M2Path )
if err != nil {
return err
}
}
2020-06-15 12:46:54 +02:00
for _ , pomFile := range pomFiles {
log . Entry ( ) . Info ( "Installing maven artifacts from module: " + pomFile )
2020-06-17 19:08:43 +02:00
// Set this module's pom file as the pom file for evaluating the packaging,
// otherwise we would evaluate the root pom in all iterations.
2020-10-30 11:04:38 +02:00
evaluateProjectPackagingOptions := * options
2020-06-17 19:08:43 +02:00
evaluateProjectPackagingOptions . PomPath = pomFile
2020-11-10 18:14:55 +02:00
packaging , err := Evaluate ( & evaluateProjectPackagingOptions , "project.packaging" , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
2020-06-17 19:08:43 +02:00
currentModuleDir := filepath . Dir ( pomFile )
// Use flat pom if available to avoid issues with unresolved variables.
pathToPomFile := pomFile
flattenedPomExists , _ := utils . FileExists ( filepath . Join ( currentModuleDir , ".flattened-pom.xml" ) )
if flattenedPomExists {
pathToPomFile = filepath . Join ( currentModuleDir , ".flattened-pom.xml" )
2020-06-15 12:46:54 +02:00
}
if packaging == "pom" {
2020-11-10 18:14:55 +02:00
err = InstallFile ( "" , pathToPomFile , options , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
} else {
2020-06-17 19:08:43 +02:00
2020-11-10 18:14:55 +02:00
err = installJarWarArtifacts ( pathToPomFile , currentModuleDir , options , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
}
}
return err
}
2020-11-10 18:14:55 +02:00
func installJarWarArtifacts ( pomFile , dir string , options * EvaluateOptions , utils Utils ) error {
2020-06-17 19:08:43 +02:00
options . PomPath = filepath . Join ( dir , "pom.xml" )
2020-11-10 18:14:55 +02:00
finalName , err := Evaluate ( options , "project.build.finalName" , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
if finalName == "" {
log . Entry ( ) . Warn ( "project.build.finalName is empty, skipping install of artifact. Installing only the pom file." )
2020-11-10 18:14:55 +02:00
err = InstallFile ( "" , pomFile , options , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
return nil
}
2020-06-17 19:08:43 +02:00
jarExists , _ := utils . FileExists ( jarFile ( dir , finalName ) )
warExists , _ := utils . FileExists ( warFile ( dir , finalName ) )
classesJarExists , _ := utils . FileExists ( classesJarFile ( dir , finalName ) )
2020-08-06 15:12:21 +02:00
originalJarExists , _ := utils . FileExists ( originalJarFile ( dir , finalName ) )
2020-06-17 19:08:43 +02:00
log . Entry ( ) . Infof ( "JAR file with name %s does exist: %t" , jarFile ( dir , finalName ) , jarExists )
log . Entry ( ) . Infof ( "Classes-JAR file with name %s does exist: %t" , classesJarFile ( dir , finalName ) , classesJarExists )
2020-08-06 15:12:21 +02:00
log . Entry ( ) . Infof ( "Original-JAR file with name %s does exist: %t" , originalJarFile ( dir , finalName ) , originalJarExists )
log . Entry ( ) . Infof ( "WAR file with name %s does exist: %t" , warFile ( dir , finalName ) , warExists )
2020-06-15 12:46:54 +02:00
2020-08-06 15:12:21 +02:00
// Due to spring's jar repackaging we need to check for an "original" jar file because the repackaged one is no suitable source for dependent maven modules
if originalJarExists {
2020-11-10 18:14:55 +02:00
err = InstallFile ( originalJarFile ( dir , finalName ) , pomFile , options , utils )
2020-08-06 15:12:21 +02:00
if err != nil {
return err
}
} else if jarExists {
2020-11-10 18:14:55 +02:00
err = InstallFile ( jarFile ( dir , finalName ) , pomFile , options , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
}
if warExists {
2020-11-10 18:14:55 +02:00
err = InstallFile ( warFile ( dir , finalName ) , pomFile , options , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
}
if classesJarExists {
2020-11-10 18:14:55 +02:00
err = InstallFile ( classesJarFile ( dir , finalName ) , pomFile , options , utils )
2020-06-15 12:46:54 +02:00
if err != nil {
return err
}
}
return nil
}
2020-06-17 19:08:43 +02:00
func jarFile ( dir , finalName string ) string {
return filepath . Join ( dir , "target" , finalName + ".jar" )
2020-06-15 12:46:54 +02:00
}
2020-06-17 19:08:43 +02:00
func classesJarFile ( dir , finalName string ) string {
return filepath . Join ( dir , "target" , finalName + "-classes.jar" )
2020-06-15 12:46:54 +02:00
}
2020-08-06 15:12:21 +02:00
func originalJarFile ( dir , finalName string ) string {
return filepath . Join ( dir , "target" , finalName + ".jar.original" )
}
2020-06-17 19:08:43 +02:00
func warFile ( dir , finalName string ) string {
return filepath . Join ( dir , "target" , finalName + ".war" )
2020-06-15 12:46:54 +02:00
}
2020-11-10 18:14:55 +02:00
func flattenPom ( options * EvaluateOptions , utils Utils ) error {
2020-06-15 12:46:54 +02:00
mavenOptionsFlatten := ExecuteOptions {
2020-10-29 17:33:58 +02:00
Goals : [ ] string { "flatten:flatten" } ,
Defines : [ ] string { "-Dflatten.mode=resolveCiFriendliesOnly" } ,
2020-11-25 18:03:11 +02:00
PomPath : options . PomPath ,
2020-11-10 18:14:55 +02:00
M2Path : options . M2Path ,
ProjectSettingsFile : options . ProjectSettingsFile ,
GlobalSettingsFile : options . GlobalSettingsFile ,
2020-06-15 12:46:54 +02:00
}
2020-11-10 18:14:55 +02:00
_ , err := Execute ( & mavenOptionsFlatten , utils )
2020-06-15 12:46:54 +02:00
return err
}
2020-06-11 14:02:54 +02:00
func evaluateStdOut ( options * ExecuteOptions ) ( * bytes . Buffer , io . Writer ) {
2020-02-28 14:09:46 +02:00
var stdOutBuf * bytes . Buffer
2020-05-06 13:35:40 +02:00
stdOut := log . Writer ( )
2020-06-11 14:02:54 +02:00
if options . ReturnStdout {
2020-02-28 14:09:46 +02:00
stdOutBuf = new ( bytes . Buffer )
stdOut = io . MultiWriter ( stdOut , stdOutBuf )
}
return stdOutBuf , stdOut
}
2020-11-10 18:14:55 +02:00
func getParametersFromOptions ( options * ExecuteOptions , utils Utils ) ( [ ] string , error ) {
2020-02-28 14:09:46 +02:00
var parameters [ ] string
2020-11-10 18:14:55 +02:00
parameters , err := DownloadAndGetMavenParameters ( options . GlobalSettingsFile , options . ProjectSettingsFile , utils )
2020-07-30 10:35:46 +02:00
if err != nil {
return nil , err
2020-02-28 14:09:46 +02:00
}
if options . M2Path != "" {
parameters = append ( parameters , "-Dmaven.repo.local=" + options . M2Path )
}
if options . PomPath != "" {
parameters = append ( parameters , "--file" , options . PomPath )
}
if options . Flags != nil {
parameters = append ( parameters , options . Flags ... )
}
if options . Defines != nil {
parameters = append ( parameters , options . Defines ... )
}
2020-04-24 10:41:49 +02:00
if ! options . LogSuccessfulMavenTransfers {
2020-02-28 14:09:46 +02:00
parameters = append ( parameters , "-Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn" )
}
2020-04-24 10:41:49 +02:00
parameters = append ( parameters , "--batch-mode" )
2020-02-28 14:09:46 +02:00
parameters = append ( parameters , options . Goals ... )
2020-05-06 17:43:32 +02:00
2020-03-17 09:33:35 +02:00
return parameters , nil
2020-02-28 14:09:46 +02:00
}
2020-11-10 18:14:55 +02:00
// GetTestModulesExcludes return testing modules that you be excluded from reactor
func GetTestModulesExcludes ( utils Utils ) [ ] string {
2020-03-02 18:04:49 +02:00
var excludes [ ] string
2020-04-24 10:41:49 +02:00
exists , _ := utils . FileExists ( "unit-tests/pom.xml" )
2020-03-02 18:04:49 +02:00
if exists {
excludes = append ( excludes , "-pl" , "!unit-tests" )
}
2020-04-24 10:41:49 +02:00
exists , _ = utils . FileExists ( "integration-tests/pom.xml" )
2020-03-02 18:04:49 +02:00
if exists {
excludes = append ( excludes , "-pl" , "!integration-tests" )
}
return excludes
}