github.com/jfrog/jfrog-cli-core@v1.12.1/artifactory/commands/pip/install.go (about) 1 package pip 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/jfrog/jfrog-cli-core/artifactory/utils" 10 piputils "github.com/jfrog/jfrog-cli-core/artifactory/utils/pip" 11 "github.com/jfrog/jfrog-cli-core/artifactory/utils/pip/dependencies" 12 "github.com/jfrog/jfrog-cli-core/utils/config" 13 "github.com/jfrog/jfrog-client-go/artifactory/buildinfo" 14 "github.com/jfrog/jfrog-client-go/utils/errorutils" 15 "github.com/jfrog/jfrog-client-go/utils/io/fileutils" 16 "github.com/jfrog/jfrog-client-go/utils/log" 17 ) 18 19 type PipInstallCommand struct { 20 *PipCommand 21 buildConfiguration *utils.BuildConfiguration 22 shouldCollectBuildInfo bool 23 } 24 25 func NewPipInstallCommand() *PipInstallCommand { 26 return &PipInstallCommand{PipCommand: &PipCommand{}} 27 } 28 29 func (pic *PipInstallCommand) Run() error { 30 log.Info("Running pip Install.") 31 32 pythonExecutablePath, err := pic.prepare() 33 if err != nil { 34 return err 35 } 36 37 pipInstaller := &piputils.PipInstaller{Args: pic.args, ServerDetails: pic.rtDetails, Repository: pic.repository, ShouldParseLogs: pic.shouldCollectBuildInfo} 38 err = pipInstaller.Install() 39 if err != nil { 40 pic.cleanBuildInfoDir() 41 return err 42 } 43 44 if !pic.shouldCollectBuildInfo { 45 log.Info("pip install finished successfully.") 46 return nil 47 } 48 49 // Collect build-info. 50 if err := pic.collectBuildInfo(pythonExecutablePath, pipInstaller.DependencyToFileMap); err != nil { 51 pic.cleanBuildInfoDir() 52 return err 53 } 54 55 log.Info("pip install finished successfully.") 56 return nil 57 } 58 59 func (pic *PipInstallCommand) collectBuildInfo(pythonExecutablePath string, dependencyToFileMap map[string]string) error { 60 if err := pic.determineModuleName(pythonExecutablePath); err != nil { 61 return err 62 } 63 64 allDependencies := pic.getAllDependencies(dependencyToFileMap) 65 dependenciesCache, err := dependencies.GetProjectDependenciesCache() 66 if err != nil { 67 return err 68 } 69 70 // Populate dependencies information - checksums and file-name. 71 servicesManager, err := utils.CreateServiceManager(pic.rtDetails, -1, false) 72 if err != nil { 73 return err 74 } 75 missingDeps, err := dependencies.AddDepsInfoAndReturnMissingDeps(allDependencies, dependenciesCache, dependencyToFileMap, servicesManager, pic.repository) 76 if err != nil { 77 return err 78 } 79 80 promptMissingDependencies(missingDeps) 81 dependencies.UpdateDependenciesCache(allDependencies) 82 pic.saveBuildInfo(allDependencies) 83 return nil 84 } 85 86 // Convert dependencyToFileMap to Dependencies map. 87 func (pic *PipInstallCommand) getAllDependencies(dependencyToFileMap map[string]string) map[string]*buildinfo.Dependency { 88 dependenciesMap := make(map[string]*buildinfo.Dependency, len(dependencyToFileMap)) 89 for depName := range dependencyToFileMap { 90 dependenciesMap[depName] = &buildinfo.Dependency{Id: depName} 91 } 92 93 return dependenciesMap 94 } 95 96 func (pic *PipInstallCommand) saveBuildInfo(allDependencies map[string]*buildinfo.Dependency) { 97 buildInfo := &buildinfo.BuildInfo{} 98 var modules []buildinfo.Module 99 var projectDependencies []buildinfo.Dependency 100 101 for _, dep := range allDependencies { 102 projectDependencies = append(projectDependencies, *dep) 103 } 104 105 // Save build-info. 106 module := buildinfo.Module{Id: pic.buildConfiguration.Module, Type: buildinfo.Pip, Dependencies: projectDependencies} 107 modules = append(modules, module) 108 109 buildInfo.Modules = modules 110 utils.SaveBuildInfo(pic.buildConfiguration.BuildName, pic.buildConfiguration.BuildNumber, pic.buildConfiguration.Project, buildInfo) 111 } 112 113 func (pic *PipInstallCommand) determineModuleName(pythonExecutablePath string) error { 114 // If module-name was set in command, don't change it. 115 if pic.buildConfiguration.Module != "" { 116 return nil 117 } 118 119 // Get package-name. 120 moduleName, err := getPackageName(pythonExecutablePath, pic.args) 121 if err != nil { 122 return err 123 } 124 125 // If package-name unknown, set module as build-name. 126 if moduleName == "" { 127 moduleName = pic.buildConfiguration.BuildName 128 log.Debug(fmt.Sprintf("Using build name: %s as module name.", moduleName)) 129 } 130 131 pic.buildConfiguration.Module = moduleName 132 return nil 133 } 134 135 func (pic *PipInstallCommand) prepare() (pythonExecutablePath string, err error) { 136 log.Debug("Preparing prerequisites.") 137 138 pythonExecutablePath, err = piputils.GetExecutablePath("python") 139 if err != nil { 140 return 141 } 142 143 pic.args, pic.buildConfiguration, err = utils.ExtractBuildDetailsFromArgs(pic.args) 144 if err != nil { 145 return 146 } 147 148 // Prepare build-info. 149 if pic.buildConfiguration.BuildName != "" && pic.buildConfiguration.BuildNumber != "" { 150 pic.shouldCollectBuildInfo = true 151 if err = utils.SaveBuildGeneralDetails(pic.buildConfiguration.BuildName, pic.buildConfiguration.BuildNumber, pic.buildConfiguration.Project); err != nil { 152 return 153 } 154 } 155 156 return 157 } 158 159 func getPackageName(pythonExecutablePath string, pipArgs []string) (string, error) { 160 // Build uses setup.py file. 161 // Setup.py should be in current dir. 162 filePath, err := getSetupPyFilePath() 163 if err != nil || filePath == "" { 164 // Error was returned or setup.py does not exist in directory. 165 return "", err 166 } 167 168 // Extract package name from setup.py. 169 packageName, err := piputils.ExtractPackageNameFromSetupPy(filePath, pythonExecutablePath) 170 if err != nil { 171 // If setup.py egg_info command failed we use build name as module name and continue to pip-install execution 172 log.Info("Couldn't determine module-name after running the 'egg_info' command: " + err.Error()) 173 return "", nil 174 } 175 return packageName, err 176 } 177 178 // Look for 'setup.py' file in current work dir. 179 // If found, return its absolute path. 180 func getSetupPyFilePath() (string, error) { 181 wd, err := os.Getwd() 182 if errorutils.CheckError(err) != nil { 183 return "", err 184 } 185 186 filePath := filepath.Join(wd, "setup.py") 187 // Check if setup.py exists. 188 validPath, err := fileutils.IsFileExists(filePath, false) 189 if err != nil { 190 return "", err 191 } 192 if !validPath { 193 log.Debug("Could not find setup.py file in current directory:", wd) 194 return "", nil 195 } 196 197 return filePath, nil 198 } 199 200 func (pic *PipInstallCommand) cleanBuildInfoDir() { 201 if err := utils.RemoveBuildDir(pic.buildConfiguration.BuildName, pic.buildConfiguration.BuildNumber, pic.buildConfiguration.Project); err != nil { 202 log.Error(fmt.Sprintf("Failed cleaning build-info directory: %s", err.Error())) 203 } 204 } 205 206 func promptMissingDependencies(missingDeps []string) { 207 if len(missingDeps) > 0 { 208 log.Warn(strings.Join(missingDeps, "\n")) 209 log.Warn("The pypi packages above could not be found in Artifactory or were not downloaded in this execution, therefore they are not included in the build-info.\n" + 210 "Reinstalling in clean environment or using '--no-cache-dir' and '--force-reinstall' flags (in one execution only), will force downloading and populating Artifactory with these packages, and therefore resolve the issue.") 211 } 212 } 213 214 func (pic *PipInstallCommand) CommandName() string { 215 return "rt_pip_install" 216 } 217 218 func (pic *PipInstallCommand) ServerDetails() (*config.ServerDetails, error) { 219 return pic.rtDetails, nil 220 }