github.com/SAP/cloud-mta-build-tool@v1.2.27/internal/artifacts/module_arch.go (about) 1 package artifacts 2 3 import ( 4 "fmt" 5 "gopkg.in/yaml.v2" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/pkg/errors" 11 12 "github.com/SAP/cloud-mta-build-tool/internal/archive" 13 "github.com/SAP/cloud-mta-build-tool/internal/buildops" 14 "github.com/SAP/cloud-mta-build-tool/internal/commands" 15 "github.com/SAP/cloud-mta-build-tool/internal/exec" 16 "github.com/SAP/cloud-mta-build-tool/internal/logs" 17 "github.com/SAP/cloud-mta/mta" 18 ) 19 20 const ( 21 ignore = "ignore" 22 ) 23 24 // ExecuteBuild - executes build of module from Makefile 25 func ExecuteBuild(source, target string, extensions []string, moduleName, platform string, wdGetter func() (string, error)) error { 26 if moduleName == "" { 27 return errors.New(buildFailedOnEmptyModuleMsg) 28 } 29 30 logs.Logger.Infof(buildMsg, moduleName) 31 loc, err := dir.Location(source, target, dir.Dev, extensions, wdGetter) 32 if err != nil { 33 return errors.Wrapf(err, buildFailedMsg, moduleName) 34 } 35 36 err = buildModule(loc, loc, moduleName, platform, true, true, map[string]string{}) 37 if err != nil { 38 return err 39 } 40 logs.Logger.Infof(buildFinishedMsg, moduleName) 41 return nil 42 } 43 44 // ExecuteSoloBuild - executes build of module from stand alone command 45 func ExecuteSoloBuild(source, target string, extensions []string, modulesNames []string, allDependencies bool, 46 generateMtadFlag bool, platform string, 47 wdGetter func() (string, error)) error { 48 49 if len(modulesNames) == 0 { 50 return errors.New(buildFailedOnEmptyModulesMsg) 51 } 52 53 sourceDir, err := getSoloModuleBuildAbsSource(source, wdGetter) 54 if err != nil { 55 return wrapBuildError(err, modulesNames) 56 } 57 58 loc, err := dir.Location(sourceDir, "", dir.Dev, extensions, wdGetter) 59 if err != nil { 60 return wrapBuildError(err, modulesNames) 61 } 62 63 mtaObj, err := loc.ParseFile() 64 if err != nil { 65 return err 66 } 67 68 // Fail-fast check on modules whose build results are resolved (not glob patterns), 69 // so we can give the error before building the modules. 70 // After the build we perform another check on the actual build result paths (including glob patterns) 71 err = checkResolvedBuildResultsConflicts(mtaObj, sourceDir, target, extensions, modulesNames, wdGetter) 72 if err != nil { 73 return wrapBuildError(err, modulesNames) 74 } 75 76 allModulesSorted, err := buildops.GetModulesNames(mtaObj) 77 if err != nil { 78 return wrapBuildError(err, modulesNames) 79 } 80 81 selectedModulesMap := make(map[string]bool) 82 var selectedModulesWithDependenciesMap map[string]bool 83 84 for _, moduleName := range modulesNames { 85 selectedModulesMap[moduleName] = true 86 } 87 88 if allDependencies { 89 selectedModulesWithDependenciesMap = make(map[string]bool) 90 for module := range selectedModulesMap { 91 err = collectSelectedModulesAndDependencies(mtaObj, selectedModulesWithDependenciesMap, module) 92 if err != nil { 93 return wrapBuildError(err, modulesNames) 94 } 95 } 96 } else { 97 selectedModulesWithDependenciesMap = selectedModulesMap 98 } 99 100 sortedModules := sortModules(allModulesSorted, selectedModulesWithDependenciesMap) 101 102 if allDependencies && len(sortedModules) > 1 { 103 logs.Logger.Infof(buildWithDependenciesMsg, `"`+strings.Join(sortedModules, `","`)+`"`) 104 } else if len(sortedModules) > 1 { 105 logs.Logger.Infof(multiBuildMsg, `"`+strings.Join(sortedModules, `", "`)+`"`) 106 } 107 108 packedModulePaths, err := buildModules(sourceDir, target, extensions, sortedModules, selectedModulesMap, wdGetter) 109 if err != nil { 110 return wrapBuildError(err, modulesNames) 111 } 112 113 if generateMtadFlag { 114 err = generateMtad(mtaObj, loc, target, platform, packedModulePaths, wdGetter) 115 if err != nil { 116 return wrapBuildError(err, modulesNames) 117 } 118 } 119 120 if len(modulesNames) > 1 { 121 logs.Logger.Infof(multiBuildFinishedMsg) 122 } 123 124 return nil 125 } 126 127 func checkResolvedBuildResultsConflicts(mtaObj *mta.MTA, source, target string, extensions []string, modulesNames []string, wdGetter func() (string, error)) error { 128 129 resultPathModuleNameMap := make(map[string]string) 130 131 for _, moduleName := range modulesNames { 132 133 module, err := mtaObj.GetModuleByName(moduleName) 134 if err != nil { 135 return err 136 } 137 moduleLoc, err := getModuleLocation(source, target, module.Name, extensions, wdGetter) 138 if err != nil { 139 return err 140 } 141 142 _, defaultBuildResult, err := commands.CommandProvider(*module) 143 if err != nil { 144 return err 145 } 146 targetArtifact, _, err := buildops.GetModuleTargetArtifactPath(moduleLoc, false, module, defaultBuildResult, false) 147 if err != nil { 148 return err 149 } 150 151 // we ignore glob patterns and only check actual paths here because the build results don't exist yet 152 if strings.ContainsAny(targetArtifact, "*?[]") { 153 continue 154 } 155 moduleName, pathInUse := resultPathModuleNameMap[targetArtifact] 156 if pathInUse { 157 return errors.Errorf(multiBuildWithPathsConflictMsg, module.Name, moduleName, filepath.Dir(targetArtifact), filepath.Base(targetArtifact)) 158 } 159 resultPathModuleNameMap[targetArtifact] = module.Name 160 } 161 162 return nil 163 } 164 165 func generateMtad(mtaObj *mta.MTA, loc dir.ITargetPath, target string, 166 platform string, packedModulePaths map[string]string, wdGetter func() (string, error)) error { 167 168 platform, err := validatePlatform(platform) 169 if err != nil { 170 return err 171 } 172 173 mtadTargetPath, err := getMtadPath(target, wdGetter) 174 if err != nil { 175 return err 176 } 177 mtadLocation := mtadLoc{path: mtadTargetPath} 178 179 return genMtad(mtaObj, &mtadLocation, loc, false, platform, false, packedModulePaths, yaml.Marshal) 180 } 181 182 func getMtadPath(target string, wdGetter func() (string, error)) (string, error) { 183 if target != "" { 184 return target, nil 185 } 186 return wdGetter() 187 } 188 189 func wrapBuildError(err error, modules []string) error { 190 if len(modules) == 1 { 191 return errors.Wrapf(err, buildFailedMsg, modules[0]) 192 } 193 return errors.Wrapf(err, multiBuildFailedMsg) 194 } 195 196 func collectSelectedModulesAndDependencies(mtaObj *mta.MTA, modulesWithDependencies map[string]bool, moduleName string) error { 197 198 if modulesWithDependencies[moduleName] { 199 return nil 200 } 201 202 modulesWithDependencies[moduleName] = true 203 module, err := mtaObj.GetModuleByName(moduleName) 204 if err != nil { 205 return err 206 } 207 for _, requires := range buildops.GetBuildRequires(module) { 208 requiredModule, err := mtaObj.GetModuleByName(requires.Name) 209 if err != nil { 210 return err 211 } 212 213 err = collectSelectedModulesAndDependencies(mtaObj, modulesWithDependencies, requiredModule.Name) 214 if err != nil { 215 return err 216 } 217 } 218 return nil 219 } 220 221 func buildModules(source, target string, extensions []string, modulesToBuild []string, 222 modulesToPack map[string]bool, wdGetter func() (string, error)) (packedModulePaths map[string]string, err error) { 223 224 buildResults := make(map[string]string) 225 for _, module := range modulesToBuild { 226 err := buildSelectedModule(source, target, extensions, module, modulesToPack[module], buildResults, wdGetter) 227 228 if err != nil { 229 return nil, err 230 } 231 } 232 233 packedModulePaths = make(map[string]string) 234 for buildResult, moduleName := range buildResults { 235 packedModulePaths[moduleName] = buildResult 236 } 237 return packedModulePaths, nil 238 } 239 240 func buildSelectedModule(source, target string, extensions []string, module string, 241 toPack bool, buildResults map[string]string, wdGetter func() (string, error)) error { 242 243 logs.Logger.Infof(buildMsg, module) 244 245 moduleLoc, err := getModuleLocation(source, target, module, extensions, wdGetter) 246 if err != nil { 247 return err 248 } 249 250 err = buildModule(moduleLoc, moduleLoc, module, "", false, toPack, buildResults) 251 if err != nil { 252 return err 253 } 254 255 logs.Logger.Infof(buildFinishedMsg, module) 256 return nil 257 } 258 259 func sortModules(allModulesSorted []string, selectedModulesMap map[string]bool) []string { 260 var result []string 261 for _, module := range allModulesSorted { 262 _, selected := selectedModulesMap[module] 263 if selected { 264 result = append(result, module) 265 } 266 } 267 return result 268 } 269 270 func getModuleLocation(source, target, moduleName string, extensions []string, wdGetter func() (string, error)) (*dir.ModuleLoc, error) { 271 targetDir, err := getSoloModuleBuildAbsTarget(source, target, moduleName, wdGetter) 272 if err != nil { 273 return nil, err 274 } 275 276 loc, err := dir.Location(source, targetDir, dir.Dev, extensions, wdGetter) 277 if err != nil { 278 return nil, err 279 } 280 281 return dir.ModuleLocation(loc, target != ""), nil 282 } 283 284 func getSoloModuleBuildAbsSource(source string, wdGetter func() (string, error)) (string, error) { 285 if source == "" { 286 return wdGetter() 287 } 288 return filepath.Abs(source) 289 } 290 291 func getSoloModuleBuildAbsTarget(absSource, target, moduleName string, wdGetter func() (string, error)) (string, error) { 292 if target != "" { 293 return filepath.Abs(target) 294 } 295 296 target, err := wdGetter() 297 if err != nil { 298 return "", err 299 } 300 _, projectFolderName := filepath.Split(absSource) 301 tmpFolderName := "." + projectFolderName + dir.TempFolderSuffix 302 303 // default target is <current folder>/.<project folder>_mta_tmp/<module_name> 304 return filepath.Join(target, tmpFolderName, moduleName), nil 305 } 306 307 // ExecutePack - executes packing of module 308 func ExecutePack(source, target string, extensions []string, moduleName, platform string, wdGetter func() (string, error)) error { 309 logs.Logger.Infof(packMsg, moduleName) 310 311 loc, err := dir.Location(source, target, dir.Dev, extensions, wdGetter) 312 if err != nil { 313 return errors.Wrapf(err, packFailedOnLocMsg, moduleName) 314 } 315 // validate platform 316 platform, err = validatePlatform(platform) 317 if err != nil { 318 return err 319 } 320 321 module, _, defaultBuildResult, err := commands.GetModuleAndCommands(loc, moduleName) 322 if err != nil { 323 return errors.Wrapf(err, packFailedOnCommandsMsg, moduleName) 324 } 325 326 if buildops.IfNoSource(module) { 327 logs.Logger.Infof(packSkippedMsg, module.Name) 328 return nil 329 } 330 331 if module.Path == "" { 332 return fmt.Errorf(packFailedOnEmptyPathMsg, moduleName) 333 } 334 335 err = packModule(loc, module, moduleName, platform, defaultBuildResult, true, map[string]string{}) 336 if err != nil { 337 return err 338 } 339 340 return nil 341 } 342 343 // buildModule - builds module 344 func buildModule(mtaParser dir.IMtaParser, moduleLoc dir.IModule, moduleName, platform string, 345 checkPlatform bool, toPack bool, buildResults map[string]string) error { 346 347 var err error 348 if checkPlatform { 349 // validate platform 350 platform, err = validatePlatform(platform) 351 if err != nil { 352 return err 353 } 354 } 355 356 // Get module respective command's to execute 357 module, mCmd, defaultBuildResults, err := commands.GetModuleAndCommands(mtaParser, moduleName) 358 if err != nil { 359 return errors.Wrapf(err, buildFailedOnCommandsMsg, moduleName) 360 } 361 362 if buildops.IfNoSource(module) { 363 logs.Logger.Infof(buildSkippedMsg, module.Name) 364 return nil 365 } 366 367 if module.Path == "" { 368 return fmt.Errorf(buildFailedOnEmptyPathMsg, moduleName) 369 } 370 371 // Development descriptor - build includes: 372 // 1. module dependencies processing 373 e := buildops.ProcessDependencies(mtaParser, moduleLoc, moduleName) 374 if e != nil { 375 return errors.Wrapf(e, buildFailedOnDepsMsg, moduleName) 376 } 377 378 // 2. module type dependent commands execution 379 modulePath := moduleLoc.GetSourceModuleDir(module.Path) 380 381 // Get module commands 382 commandList, e := commands.CmdConverter(modulePath, mCmd) 383 if e != nil { 384 return errors.Wrapf(e, buildFailedOnCommandsMsg, moduleName) 385 } 386 387 // Execute child-process with module respective commands 388 var timeout string 389 if module.BuildParams != nil && module.BuildParams["timeout"] != nil { 390 var ok bool 391 timeout, ok = module.BuildParams["timeout"].(string) 392 if !ok { 393 return errors.Errorf(exec.ExecInvalidTimeoutMsg, fmt.Sprint(module.BuildParams["timeout"])) 394 } 395 } 396 e = exec.ExecuteWithTimeout(commandList, timeout, true) 397 if e != nil { 398 return errors.Wrapf(e, buildFailedMsg, moduleName) 399 } 400 401 if toPack { 402 // 3. Packing the modules build artifacts (include node modules) 403 // into the artifactsPath dir as data zip 404 return packModule(moduleLoc, module, moduleName, platform, defaultBuildResults, checkPlatform, buildResults) 405 } 406 407 return nil 408 } 409 410 // packModule - pack build module artifacts 411 func packModule(moduleLoc dir.IModule, module *mta.Module, moduleName, platform, defaultBuildResult string, 412 checkPlatform bool, buildResults map[string]string) error { 413 414 if checkPlatform && !buildops.PlatformDefined(module, platform) { 415 return nil 416 } 417 418 logs.Logger.Info(fmt.Sprintf(buildResultMsg, moduleName, moduleLoc.GetTargetModuleDir(moduleName))) 419 420 sourceArtifact, err := buildops.GetModuleSourceArtifactPath(moduleLoc, false, module, defaultBuildResult, true) 421 if err != nil { 422 return errors.Wrapf(err, packFailedOnBuildArtifactMsg, moduleName) 423 } 424 targetArtifact, toArchive, err := buildops.GetModuleTargetArtifactPath(moduleLoc, false, module, defaultBuildResult, true) 425 if err != nil { 426 return errors.Wrapf(err, packFailedOnTargetArtifactMsg, moduleName) 427 } 428 429 conflictingModule, ok := buildResults[targetArtifact] 430 if ok { 431 return fmt.Errorf(multiBuildWithPathsConflictMsg, conflictingModule, module.Name, filepath.Dir(targetArtifact), filepath.Base(targetArtifact)) 432 } 433 buildResults[targetArtifact] = moduleName 434 435 if !toArchive { 436 return copyModuleArchiveToResultDir(sourceArtifact, targetArtifact, moduleName) 437 } 438 439 return archiveModuleToResultDir(sourceArtifact, targetArtifact, getIgnores(moduleLoc, module, sourceArtifact), moduleName) 440 } 441 442 func copyModuleArchiveToResultDir(source, target, moduleName string) error { 443 // Create empty folder with name as before the zip process 444 // to put the file such as data.zip inside 445 modulePathInTmpFolder := filepath.Dir(target) 446 err := dir.CreateDirIfNotExist(modulePathInTmpFolder) 447 if err != nil { 448 return errors.Wrapf(err, packFailedOnFolderCreationMsg, moduleName, modulePathInTmpFolder) 449 } 450 451 err = dir.CopyFile(source, target) 452 if err != nil { 453 return errors.Wrapf(err, packFailedOnCopyMsg, moduleName, source, target) 454 } 455 return nil 456 } 457 458 func archiveModuleToResultDir(buildResult string, requestedResultFileName string, ignore []string, moduleName string) error { 459 // Archive the folder without the ignored files and/or subfolders, which are excluded from the package. 460 err := dir.Archive(buildResult, requestedResultFileName, ignore) 461 if err != nil { 462 return errors.Wrapf(err, PackFailedOnArchMsg, moduleName) 463 } 464 return nil 465 } 466 467 // getIgnores - get files and/or subfolders to exclude from the package. 468 func getIgnores(moduleLoc dir.IModule, module *mta.Module, moduleResultPath string) []string { 469 var ignoreList []string 470 // ignore defined in build params is declared 471 if module.BuildParams != nil && module.BuildParams[ignore] != nil { 472 ignoreList = convert(module.BuildParams[ignore].([]interface{})) 473 } 474 // we add target folder to the list of ignores to avoid it's packaging 475 // it can be the case only when target folder is subfolder (on any level) of the archived folder path 476 // the ignored folder is the root where all the build results are created, even if we are building more than one module 477 targetFolder := moduleLoc.GetTargetTmpRoot() 478 relativeTarget, err := filepath.Rel(moduleResultPath, targetFolder) 479 if err == nil && !(relativeTarget == ".." || strings.HasPrefix(relativeTarget, ".."+string(os.PathSeparator))) { 480 ignoreList = append(ignoreList, relativeTarget) 481 } 482 483 return ignoreList 484 } 485 486 // Convert slice []interface{} to slice []string 487 func convert(data []interface{}) []string { 488 aString := make([]string, len(data)) 489 for i, v := range data { 490 aString[i] = v.(string) 491 } 492 return aString 493 } 494 495 // CopyMtaContent copies the content of all modules and resources which are presented in the deployment descriptor, 496 // in the source directory, to the target directory 497 func CopyMtaContent(source, target string, extensions []string, copyInParallel bool, wdGetter func() (string, error)) error { 498 499 logs.Logger.Info(copyStartMsg) 500 loc, err := dir.Location(source, target, dir.Dep, extensions, wdGetter) 501 if err != nil { 502 return errors.Wrap(err, copyContentFailedOnLocMsg) 503 } 504 mtaObj, err := loc.ParseFile() 505 if err != nil { 506 return errors.Wrapf(err, copyContentFailedMsg) 507 } 508 err = copyModuleContent(loc.GetSource(), loc.GetTargetTmpDir(), mtaObj, copyInParallel) 509 if err != nil { 510 return err 511 } 512 513 err = copyRequiredDependencyContent(loc.GetSource(), loc.GetTargetTmpDir(), mtaObj, copyInParallel) 514 if err != nil { 515 return err 516 } 517 518 return copyResourceContent(loc.GetSource(), loc.GetTargetTmpDir(), mtaObj, copyInParallel) 519 } 520 521 func copyModuleContent(source, target string, mta *mta.MTA, copyInParallel bool) error { 522 return copyMtaContent(source, target, getModulesWithPaths(mta.Modules), copyInParallel) 523 } 524 525 func copyResourceContent(source, target string, mta *mta.MTA, copyInParallel bool) error { 526 return copyMtaContent(source, target, getResourcesPaths(mta.Resources), copyInParallel) 527 } 528 529 func copyRequiredDependencyContent(source, target string, mta *mta.MTA, copyInParallel bool) error { 530 return copyMtaContent(source, target, getRequiredDependencyPaths(mta.Modules), copyInParallel) 531 } 532 533 func getRequiredDependencyPaths(mtaModules []*mta.Module) []string { 534 result := make([]string, 0) 535 for _, module := range mtaModules { 536 requiredDependenciesWithPaths := getRequiredDependenciesWithPathsForModule(module) 537 result = append(result, requiredDependenciesWithPaths...) 538 } 539 return result 540 } 541 542 func getRequiredDependenciesWithPathsForModule(module *mta.Module) []string { 543 result := make([]string, 0) 544 for _, requiredDependency := range module.Requires { 545 if requiredDependency.Parameters["path"] != nil { 546 result = append(result, requiredDependency.Parameters["path"].(string)) 547 } 548 } 549 return result 550 } 551 func copyMtaContent(source, target string, mtaPaths []string, copyInParallel bool) error { 552 copiedMtaContents := make([]string, 0) 553 for _, mtaPath := range mtaPaths { 554 sourceMtaContent := filepath.Join(source, mtaPath) 555 if doesNotExist(sourceMtaContent) { 556 return handleCopyMtaContentFailure(target, copiedMtaContents, pathNotExistsMsg, []interface{}{mtaPath}) 557 } 558 copiedMtaContents = append(copiedMtaContents, mtaPath) 559 targetMtaContent := filepath.Join(target, mtaPath) 560 err := copyMtaContentFromPath(sourceMtaContent, targetMtaContent, mtaPath, target, copyInParallel) 561 if err != nil { 562 return handleCopyMtaContentFailure(target, copiedMtaContents, copyContentCopyFailedMsg, []interface{}{mtaPath, source, err.Error()}) 563 } 564 logs.Logger.Debugf(copyDoneMsg, mtaPath) 565 } 566 567 return nil 568 } 569 570 func handleCopyMtaContentFailure(targetLocation string, copiedMtaContents []string, 571 message string, messageArguments []interface{}) error { 572 errCleanup := cleanUpCopiedContent(targetLocation, copiedMtaContents) 573 if errCleanup == nil { 574 return errors.Errorf(message, messageArguments...) 575 } 576 return errors.Errorf(message+"; "+cleanupFailedMsg, messageArguments...) 577 } 578 579 func copyMtaContentFromPath(sourceMtaContent, targetMtaContent, mtaContentPath, target string, copyInParallel bool) error { 580 mtaContentInfo, _ := os.Stat(sourceMtaContent) 581 if mtaContentInfo.IsDir() { 582 if copyInParallel { 583 return dir.CopyDir(sourceMtaContent, targetMtaContent, true, dir.CopyEntriesInParallel) 584 } 585 return dir.CopyDir(sourceMtaContent, targetMtaContent, true, dir.CopyEntries) 586 } 587 588 mtaContentParentDir := filepath.Dir(mtaContentPath) 589 err := dir.CreateDirIfNotExist(filepath.Join(target, mtaContentParentDir)) 590 if err != nil { 591 return err 592 } 593 return dir.CopyFileWithMode(sourceMtaContent, targetMtaContent, mtaContentInfo.Mode()) 594 } 595 596 func cleanUpCopiedContent(targetLocation string, copiendMtaContents []string) error { 597 for _, copiedMtaContent := range copiendMtaContents { 598 err := os.RemoveAll(filepath.Join(targetLocation, copiedMtaContent)) 599 if err != nil { 600 return err 601 } 602 } 603 return nil 604 } 605 606 func doesNotExist(path string) bool { 607 _, err := os.Stat(path) 608 return os.IsNotExist(err) 609 } 610 611 func getModulesWithPaths(mtaModules []*mta.Module) []string { 612 result := make([]string, 0) 613 for _, module := range mtaModules { 614 if module.Path != "" { 615 result = append(result, module.Path) 616 } 617 } 618 return result 619 } 620 621 func getResourcesPaths(resources []*mta.Resource) []string { 622 result := make([]string, 0) 623 for _, resource := range resources { 624 if resource.Parameters["path"] != nil { 625 result = append(result, resource.Parameters["path"].(string)) 626 } 627 } 628 return result 629 }