github.com/SAP/jenkins-library@v1.362.0/cmd/pythonBuild.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"path/filepath"
     6  
     7  	"github.com/SAP/jenkins-library/pkg/buildsettings"
     8  	"github.com/SAP/jenkins-library/pkg/command"
     9  	"github.com/SAP/jenkins-library/pkg/log"
    10  	"github.com/SAP/jenkins-library/pkg/piperutils"
    11  	"github.com/SAP/jenkins-library/pkg/telemetry"
    12  )
    13  
    14  const (
    15  	PyBomFilename           = "bom-pip.xml"
    16  	stepName                = "pythonBuild"
    17  	cycloneDxPackageVersion = "cyclonedx-bom==3.11.0"
    18  	cycloneDxSchemaVersion  = "1.4"
    19  )
    20  
    21  type pythonBuildUtils interface {
    22  	command.ExecRunner
    23  	FileExists(filename string) (bool, error)
    24  	piperutils.FileUtils
    25  }
    26  
    27  type pythonBuildUtilsBundle struct {
    28  	*command.Command
    29  	*piperutils.Files
    30  }
    31  
    32  func newPythonBuildUtils() pythonBuildUtils {
    33  	utils := pythonBuildUtilsBundle{
    34  		Command: &command.Command{
    35  			StepName: "pythonBuild",
    36  		},
    37  		Files: &piperutils.Files{},
    38  	}
    39  	// Reroute command output to logging framework
    40  	utils.Stdout(log.Writer())
    41  	utils.Stderr(log.Writer())
    42  	return &utils
    43  }
    44  
    45  func pythonBuild(config pythonBuildOptions, telemetryData *telemetry.CustomData, commonPipelineEnvironment *pythonBuildCommonPipelineEnvironment) {
    46  	utils := newPythonBuildUtils()
    47  
    48  	err := runPythonBuild(&config, telemetryData, utils, commonPipelineEnvironment)
    49  	if err != nil {
    50  		log.Entry().WithError(err).Fatal("step execution failed")
    51  	}
    52  }
    53  
    54  func runPythonBuild(config *pythonBuildOptions, telemetryData *telemetry.CustomData, utils pythonBuildUtils, commonPipelineEnvironment *pythonBuildCommonPipelineEnvironment) error {
    55  
    56  	pipInstallFlags := []string{"install", "--upgrade"}
    57  	virutalEnvironmentPathMap := make(map[string]string)
    58  
    59  	err := createVirtualEnvironment(utils, config, virutalEnvironmentPathMap)
    60  	if err != nil {
    61  		return err
    62  	}
    63  
    64  	err = buildExecute(config, utils, pipInstallFlags, virutalEnvironmentPathMap)
    65  	if err != nil {
    66  		return fmt.Errorf("Python build failed with error: %w", err)
    67  	}
    68  
    69  	if config.CreateBOM {
    70  		if err := runBOMCreationForPy(utils, pipInstallFlags, virutalEnvironmentPathMap, config); err != nil {
    71  			return fmt.Errorf("BOM creation failed: %w", err)
    72  		}
    73  	}
    74  
    75  	log.Entry().Debugf("creating build settings information...")
    76  
    77  	dockerImage, err := GetDockerImageValue(stepName)
    78  	if err != nil {
    79  		return err
    80  	}
    81  
    82  	pythonConfig := buildsettings.BuildOptions{
    83  		CreateBOM:         config.CreateBOM,
    84  		Publish:           config.Publish,
    85  		BuildSettingsInfo: config.BuildSettingsInfo,
    86  		DockerImage:       dockerImage,
    87  	}
    88  	buildSettingsInfo, err := buildsettings.CreateBuildSettingsInfo(&pythonConfig, stepName)
    89  	if err != nil {
    90  		log.Entry().Warnf("failed to create build settings info: %v", err)
    91  	}
    92  	commonPipelineEnvironment.custom.buildSettingsInfo = buildSettingsInfo
    93  
    94  	if config.Publish {
    95  		if err := publishWithTwine(config, utils, pipInstallFlags, virutalEnvironmentPathMap); err != nil {
    96  			return fmt.Errorf("failed to publish: %w", err)
    97  		}
    98  	}
    99  
   100  	err = removeVirtualEnvironment(utils, config)
   101  	if err != nil {
   102  		return err
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  func buildExecute(config *pythonBuildOptions, utils pythonBuildUtils, pipInstallFlags []string, virutalEnvironmentPathMap map[string]string) error {
   109  
   110  	var flags []string
   111  	flags = append(flags, config.BuildFlags...)
   112  	flags = append(flags, "setup.py", "sdist", "bdist_wheel")
   113  
   114  	log.Entry().Info("starting building python project:")
   115  	err := utils.RunExecutable(virutalEnvironmentPathMap["python"], flags...)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	return nil
   120  }
   121  
   122  func createVirtualEnvironment(utils pythonBuildUtils, config *pythonBuildOptions, virutalEnvironmentPathMap map[string]string) error {
   123  	virtualEnvironmentFlags := []string{"-m", "venv", config.VirutalEnvironmentName}
   124  	err := utils.RunExecutable("python3", virtualEnvironmentFlags...)
   125  	if err != nil {
   126  		return err
   127  	}
   128  	err = utils.RunExecutable("bash", "-c", "source "+filepath.Join(config.VirutalEnvironmentName, "bin", "activate"))
   129  	if err != nil {
   130  		return err
   131  	}
   132  	virutalEnvironmentPathMap["pip"] = filepath.Join(config.VirutalEnvironmentName, "bin", "pip")
   133  	// venv will create symlinks to python3 inside the container
   134  	virutalEnvironmentPathMap["python"] = "python"
   135  	virutalEnvironmentPathMap["deactivate"] = filepath.Join(config.VirutalEnvironmentName, "bin", "deactivate")
   136  
   137  	return nil
   138  }
   139  
   140  func removeVirtualEnvironment(utils pythonBuildUtils, config *pythonBuildOptions) error {
   141  	err := utils.RemoveAll(config.VirutalEnvironmentName)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	return nil
   146  }
   147  
   148  func runBOMCreationForPy(utils pythonBuildUtils, pipInstallFlags []string, virutalEnvironmentPathMap map[string]string, config *pythonBuildOptions) error {
   149  	pipInstallOriginalFlags := pipInstallFlags
   150  	exists, _ := utils.FileExists(config.RequirementsFilePath)
   151  	if exists {
   152  		pipInstallRequirementsFlags := append(pipInstallOriginalFlags, "--requirement", config.RequirementsFilePath)
   153  		if err := utils.RunExecutable(virutalEnvironmentPathMap["pip"], pipInstallRequirementsFlags...); err != nil {
   154  			return err
   155  		}
   156  	} else {
   157  		log.Entry().Warnf("unable to find requirements.txt file at %s , continuing SBOM generation without requirements.txt", config.RequirementsFilePath)
   158  	}
   159  
   160  	pipInstallCycloneDxFlags := append(pipInstallOriginalFlags, cycloneDxPackageVersion)
   161  
   162  	if err := utils.RunExecutable(virutalEnvironmentPathMap["pip"], pipInstallCycloneDxFlags...); err != nil {
   163  		return err
   164  	}
   165  	virutalEnvironmentPathMap["cyclonedx"] = filepath.Join(config.VirutalEnvironmentName, "bin", "cyclonedx-py")
   166  
   167  	if err := utils.RunExecutable(virutalEnvironmentPathMap["cyclonedx"], "--e", "--output", PyBomFilename, "--format", "xml", "--schema-version", cycloneDxSchemaVersion); err != nil {
   168  		return err
   169  	}
   170  	return nil
   171  }
   172  
   173  func publishWithTwine(config *pythonBuildOptions, utils pythonBuildUtils, pipInstallFlags []string, virutalEnvironmentPathMap map[string]string) error {
   174  	pipInstallFlags = append(pipInstallFlags, "twine")
   175  	if err := utils.RunExecutable(virutalEnvironmentPathMap["pip"], pipInstallFlags...); err != nil {
   176  		return err
   177  	}
   178  	virutalEnvironmentPathMap["twine"] = filepath.Join(config.VirutalEnvironmentName, "bin", "twine")
   179  	if err := utils.RunExecutable(virutalEnvironmentPathMap["twine"], "upload", "--username", config.TargetRepositoryUser,
   180  		"--password", config.TargetRepositoryPassword, "--repository-url", config.TargetRepositoryURL, "--disable-progress-bar",
   181  		"dist/*"); err != nil {
   182  		return err
   183  	}
   184  	return nil
   185  }