github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/gradleExecuteBuild.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/fs"
     8  	"path/filepath"
     9  	"strings"
    10  	"text/template"
    11  
    12  	"github.com/SAP/jenkins-library/pkg/command"
    13  	"github.com/SAP/jenkins-library/pkg/gradle"
    14  	"github.com/SAP/jenkins-library/pkg/log"
    15  	"github.com/SAP/jenkins-library/pkg/piperenv"
    16  	"github.com/SAP/jenkins-library/pkg/piperutils"
    17  	"github.com/SAP/jenkins-library/pkg/telemetry"
    18  )
    19  
    20  const (
    21  	gradleBomFilename = "bom-gradle"
    22  )
    23  
    24  var (
    25  	bomGradleTaskName = "cyclonedxBom"
    26  	publishTaskName   = "publish"
    27  	pathToModuleFile  = filepath.Join("build", "publications", "maven", "module.json")
    28  	rootPath          = "."
    29  )
    30  
    31  const publishInitScriptContentTemplate = `
    32  {{ if .ApplyPublishingForAllProjects}}allprojects{{else}}rootProject{{ end }} {
    33      def gradleExecuteBuild_skipPublishingProjects = [{{ if .ApplyPublishingForAllProjects}}{{range .ExcludePublishingForProjects}} "{{.}}",{{end}}{{end}} ];
    34      if (!gradleExecuteBuild_skipPublishingProjects.contains(project.name)) {
    35          apply plugin: 'maven-publish'
    36          apply plugin: 'java'
    37          publishing {
    38              publications {
    39                  maven(MavenPublication) {
    40                      versionMapping {
    41                          usage('java-api') {
    42                              fromResolutionOf('runtimeClasspath')
    43                          }
    44                          usage('java-runtime') {
    45                              fromResolutionResult()
    46                          }
    47                      }
    48                      {{- if .ArtifactGroupID}}
    49                      groupId = '{{.ArtifactGroupID}}'
    50                      {{- end }}
    51                      {{- if .ApplyPublishingForAllProjects }}
    52                      {{else if .ArtifactID}}
    53                      artifactId = '{{.ArtifactID}}'
    54                      {{- end }}
    55                      {{- if .ArtifactVersion}}
    56                      version = '{{.ArtifactVersion}}'
    57                      {{- end }}
    58                      from components.java
    59                  }
    60              }
    61              repositories {
    62                  maven {
    63                      credentials {
    64                          username = "{{.RepositoryUsername}}"
    65                          password = "{{.RepositoryPassword}}"
    66                      }
    67                      url = "{{.RepositoryURL}}"
    68                  }
    69              }
    70          }
    71      }
    72  }
    73  `
    74  
    75  const bomInitScriptContentTemplate = `
    76  initscript {
    77    repositories {
    78      mavenCentral()
    79      maven {
    80        url "https://plugins.gradle.org/m2/"
    81      }
    82    }
    83    dependencies {
    84      classpath "org.cyclonedx:cyclonedx-gradle-plugin:1.7.0"
    85    }
    86  }
    87  
    88  allprojects {
    89      def gradleExecuteBuild_skipBOMProjects = [{{range .ExcludeCreateBOMForProjects}} "{{.}}",{{end}} ];
    90      if (!gradleExecuteBuild_skipBOMProjects.contains(project.name)) {
    91          apply plugin: 'java'
    92          apply plugin: org.cyclonedx.gradle.CycloneDxPlugin
    93  
    94          cyclonedxBom {
    95              outputName = "` + gradleBomFilename + `"
    96              outputFormat = "xml"
    97              schemaVersion = "1.4"
    98              includeConfigs = ["runtimeClasspath"]
    99              skipConfigs = ["compileClasspath", "testCompileClasspath"]
   100          }
   101      }
   102  }
   103  `
   104  
   105  // PublishedArtifacts contains information about published artifacts
   106  type PublishedArtifacts struct {
   107  	Info     Component `json:"component,omitempty"`
   108  	Elements []Element `json:"variants,omitempty"`
   109  }
   110  
   111  type Component struct {
   112  	Module string `json:"module,omitempty"`
   113  }
   114  
   115  type Element struct {
   116  	Name      string     `json:"name,omitempty"`
   117  	Artifacts []Artifact `json:"files,omitempty"`
   118  }
   119  
   120  type Artifact struct {
   121  	Name string `json:"name,omitempty"`
   122  }
   123  
   124  type WalkDir func(root string, fn fs.WalkDirFunc) error
   125  
   126  type Filepath interface {
   127  	WalkDir(root string, fn fs.WalkDirFunc) error
   128  }
   129  
   130  type WalkDirFunc func(root string, fn fs.WalkDirFunc) error
   131  
   132  func (f WalkDirFunc) WalkDir(root string, fn fs.WalkDirFunc) error {
   133  	return f(root, fn)
   134  }
   135  
   136  type gradleExecuteBuildUtils interface {
   137  	command.ExecRunner
   138  	piperutils.FileUtils
   139  	Filepath
   140  }
   141  
   142  type gradleExecuteBuildUtilsBundle struct {
   143  	*command.Command
   144  	*piperutils.Files
   145  	Filepath
   146  }
   147  
   148  func newGradleExecuteBuildUtils() gradleExecuteBuildUtils {
   149  	var walkDirFunc WalkDirFunc = filepath.WalkDir
   150  	utils := gradleExecuteBuildUtilsBundle{
   151  		Command: &command.Command{
   152  			StepName: "gradleExecuteBuild",
   153  		},
   154  		Files:    &piperutils.Files{},
   155  		Filepath: walkDirFunc,
   156  	}
   157  	utils.Stdout(log.Writer())
   158  	utils.Stderr(log.Writer())
   159  	return &utils
   160  }
   161  
   162  func gradleExecuteBuild(config gradleExecuteBuildOptions, telemetryData *telemetry.CustomData, pipelineEnv *gradleExecuteBuildCommonPipelineEnvironment) {
   163  	utils := newGradleExecuteBuildUtils()
   164  	err := runGradleExecuteBuild(&config, telemetryData, utils, pipelineEnv)
   165  	if err != nil {
   166  		log.Entry().WithError(err).Fatalf("step execution failed: %v", err)
   167  	}
   168  }
   169  
   170  func runGradleExecuteBuild(config *gradleExecuteBuildOptions, telemetryData *telemetry.CustomData, utils gradleExecuteBuildUtils, pipelineEnv *gradleExecuteBuildCommonPipelineEnvironment) error {
   171  	log.Entry().Info("BOM file creation...")
   172  
   173  	if config.CreateBOM {
   174  		if err := createBOM(config, utils); err != nil {
   175  			return err
   176  		}
   177  	}
   178  
   179  	// gradle build
   180  	// if user provides BuildFlags, it is respected over a single Task
   181  	gradleOptions := &gradle.ExecuteOptions{
   182  		BuildGradlePath: config.Path,
   183  		Task:            config.Task,
   184  		BuildFlags:      config.BuildFlags,
   185  		UseWrapper:      config.UseWrapper,
   186  	}
   187  	if _, err := gradle.Execute(gradleOptions, utils); err != nil {
   188  		log.Entry().WithError(err).Errorf("gradle build execution was failed: %v", err)
   189  		return err
   190  	}
   191  
   192  	log.Entry().Info("Publishing of artifacts to staging repository...")
   193  	if config.Publish {
   194  		if err := publishArtifacts(config, utils, pipelineEnv); err != nil {
   195  			return err
   196  		}
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  func createBOM(config *gradleExecuteBuildOptions, utils gradleExecuteBuildUtils) error {
   203  	createBOMInitScriptContent, err := getInitScriptContent(config, bomInitScriptContentTemplate)
   204  	if err != nil {
   205  		return fmt.Errorf("failed to get BOM init script content: %v", err)
   206  	}
   207  	gradleOptions := &gradle.ExecuteOptions{
   208  		BuildGradlePath:   config.Path,
   209  		Task:              bomGradleTaskName,
   210  		UseWrapper:        config.UseWrapper,
   211  		InitScriptContent: createBOMInitScriptContent,
   212  	}
   213  	if _, err := gradle.Execute(gradleOptions, utils); err != nil {
   214  		log.Entry().WithError(err).Errorf("failed to create BOM: %v", err)
   215  		return err
   216  	}
   217  	return nil
   218  }
   219  
   220  func publishArtifacts(config *gradleExecuteBuildOptions, utils gradleExecuteBuildUtils, pipelineEnv *gradleExecuteBuildCommonPipelineEnvironment) error {
   221  	publishInitScriptContent, err := getInitScriptContent(config, publishInitScriptContentTemplate)
   222  	if err != nil {
   223  		return fmt.Errorf("failed to get publish init script content: %v", err)
   224  	}
   225  	gradleOptions := &gradle.ExecuteOptions{
   226  		BuildGradlePath:   config.Path,
   227  		Task:              publishTaskName,
   228  		UseWrapper:        config.UseWrapper,
   229  		InitScriptContent: publishInitScriptContent,
   230  	}
   231  	if _, err := gradle.Execute(gradleOptions, utils); err != nil {
   232  		log.Entry().WithError(err).Errorf("failed to publish artifacts: %v", err)
   233  		return err
   234  	}
   235  	var artifacts piperenv.Artifacts
   236  	err = utils.WalkDir(rootPath, func(path string, d fs.DirEntry, err error) error {
   237  		if d.IsDir() {
   238  			return nil
   239  		}
   240  		if strings.HasSuffix(path, pathToModuleFile) {
   241  			pathArtifacts, artifactsErr := getPublishedArtifactsNames(path, utils)
   242  			if artifactsErr != nil {
   243  				return fmt.Errorf("failed to get published artifacts in path %s: %v", path, artifactsErr)
   244  			}
   245  			artifacts = append(artifacts, pathArtifacts...)
   246  		}
   247  		return nil
   248  	})
   249  	if err != nil {
   250  		return err
   251  	}
   252  	pipelineEnv.custom.artifacts = artifacts
   253  	return nil
   254  }
   255  
   256  func getInitScriptContent(options *gradleExecuteBuildOptions, templateContent string) (string, error) {
   257  	tmpl, err := template.New("resources").Parse(templateContent)
   258  	if err != nil {
   259  		return "", err
   260  	}
   261  
   262  	var generatedCode bytes.Buffer
   263  	err = tmpl.Execute(&generatedCode, options)
   264  	if err != nil {
   265  		return "", err
   266  	}
   267  
   268  	return generatedCode.String(), nil
   269  }
   270  
   271  func getPublishedArtifactsNames(file string, utils gradleExecuteBuildUtils) (piperenv.Artifacts, error) {
   272  	artifacts := piperenv.Artifacts{}
   273  	publishedArtifacts := PublishedArtifacts{}
   274  	exists, err := utils.FileExists(file)
   275  	if err != nil {
   276  		return nil, fmt.Errorf("failed to check existence of the file '%s': %v", file, err)
   277  	}
   278  	if !exists {
   279  		return nil, fmt.Errorf("failed to get '%s': file does not exist", file)
   280  	}
   281  	content, err := utils.ReadFile(file)
   282  	if err != nil {
   283  		return nil, fmt.Errorf("failed to read '%s': %v", file, err)
   284  	}
   285  	err = json.Unmarshal(content, &publishedArtifacts)
   286  	if err != nil {
   287  		return nil, fmt.Errorf("failed to unmarshal '%s': %v", file, err)
   288  	}
   289  	for _, element := range publishedArtifacts.Elements {
   290  		if element.Name != "apiElements" {
   291  			continue
   292  		}
   293  		for _, artifact := range element.Artifacts {
   294  			artifacts = append(artifacts, piperenv.Artifact{Id: publishedArtifacts.Info.Module, Name: artifact.Name})
   295  		}
   296  	}
   297  	return artifacts, nil
   298  }