github.com/SAP/cloud-mta-build-tool@v1.2.27/internal/tpl/makefile.go (about) 1 package tpl 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 "text/template" 9 10 "github.com/kballard/go-shellquote" 11 "github.com/pkg/errors" 12 13 "github.com/SAP/cloud-mta-build-tool/internal/archive" 14 "github.com/SAP/cloud-mta-build-tool/internal/buildops" 15 "github.com/SAP/cloud-mta-build-tool/internal/commands" 16 "github.com/SAP/cloud-mta-build-tool/internal/logs" 17 "github.com/SAP/cloud-mta-build-tool/internal/version" 18 "github.com/SAP/cloud-mta/mta" 19 ) 20 21 type tplCfg struct { 22 tplContent []byte 23 relPath string 24 preContent []byte 25 postContent []byte 26 depDesc string 27 } 28 29 // ExecuteMake - generate makefile 30 func ExecuteMake(source, target string, extensions []string, name, mode string, wdGetter func() (string, error), useDefaultMbt bool) error { 31 logs.Logger.Infof(`generating the "%s" file...`, name) 32 loc, err := dir.Location(source, target, dir.Dev, extensions, wdGetter) 33 if err != nil { 34 return errors.Wrapf(err, genFailedOnInitLocMsg, name) 35 } 36 err = genMakefile(loc, loc, loc, loc, loc.GetExtensionFilePaths(), name, mode, useDefaultMbt) 37 if err != nil { 38 return err 39 } 40 logs.Logger.Info("done") 41 return nil 42 } 43 44 // genMakefile - Generate the makefile 45 func genMakefile(mtaParser dir.IMtaParser, loc dir.ITargetPath, srcLoc dir.ISourceModule, desc dir.IDescriptor, extensionFilePaths []string, makeFilename, mode string, useDefaultMbt bool) error { 46 tpl, err := getTplCfg(mode, desc.IsDeploymentDescriptor()) 47 if err != nil { 48 return err 49 } 50 if err == nil { 51 tpl.depDesc = desc.GetDescriptor() 52 err = makeFile(mtaParser, loc, srcLoc, extensionFilePaths, makeFilename, &tpl, useDefaultMbt) 53 } 54 return err 55 } 56 57 type templateData struct { 58 File mta.MTA 59 Loc dir.ISourceModule 60 } 61 62 type templateDepData struct { 63 Name string 64 SourcePath string 65 TargetPath string 66 Patterns []string 67 } 68 69 // ConvertToShellArgument wraps a string in quotation marks if necessary and escapes necessary characters in it, 70 // so it can be used as a shell argument or flag in the makefile 71 func (data templateData) ConvertToShellArgument(s string) string { 72 return shellquote.Join(s) 73 } 74 75 // IsNoSource checks if module has "no-source" build parameter 76 func (data templateData) IsNoSource(moduleName string) (bool, error) { 77 module, e := data.File.GetModuleByName(moduleName) 78 if e != nil { 79 return false, e 80 } 81 noSource := buildops.IfNoSource(module) 82 if !noSource && module.Path == "" { 83 return false, errors.Errorf(noPathMsg, moduleName) 84 } 85 return noSource, nil 86 } 87 88 func (data templateData) GetModuleDeps(moduleName string) ([]templateDepData, error) { 89 module, e := data.File.GetModuleByName(moduleName) 90 if e != nil { 91 return nil, e 92 } 93 requires := buildops.GetBuildRequires(module) 94 templateDeps := make([]templateDepData, len(requires)) 95 for index, req := range requires { 96 sourcePath, targetPath, artifacts, e := buildops.GetRequiresArtifacts(data.Loc, &data.File, &req, moduleName, false) 97 if e != nil { 98 return nil, e 99 } 100 templateDeps[index] = templateDepData{req.Name, sourcePath, targetPath, artifacts} 101 } 102 return templateDeps, nil 103 } 104 105 func (data templateData) GetPathArgument(innerPath string) string { 106 path, err := filepath.Rel(data.Loc.GetSourceModuleDir("."), innerPath) 107 if err != nil { 108 // If we can't get a relative path, use the full path 109 return fmt.Sprintf(`"%s"`, innerPath) 110 } 111 return fmt.Sprintf(`"$(PROJ_DIR)/%s"`, path) 112 } 113 114 // makeFile - generate makefile form templates 115 func makeFile(mtaParser dir.IMtaParser, loc dir.ITargetPath, srcLoc dir.ISourceModule, extensionFilePaths []string, makeFilename string, tpl *tplCfg, useDefaultMbt bool) (e error) { 116 117 // template data 118 data := templateData{} 119 120 err := dir.CreateDirIfNotExist(loc.GetTarget()) 121 if err != nil { 122 return err 123 } 124 125 // ParseFile file 126 m, err := mtaParser.ParseFile() 127 if err != nil { 128 return errors.Wrapf(err, genFailedMsg, makeFilename) 129 } 130 131 // Check for circular build dependencies between the modules. The error message from make is not clear so we 132 // should give an error here during the generation of the makefile. 133 _, e = buildops.GetModulesNames(m) 134 if e != nil { 135 return e 136 } 137 138 // Template data 139 data.File = *m 140 data.Loc = srcLoc 141 142 // path for creating the file 143 target := loc.GetTarget() 144 path := filepath.Join(target, tpl.relPath) 145 146 // Create maps of the template method's 147 t, err := mapTpl(tpl.tplContent, tpl.preContent, tpl.postContent, useDefaultMbt, extensionFilePaths, path) 148 if err != nil { 149 return errors.Wrapf(err, genFailedOnTmplMapMsg, makeFilename) 150 } 151 // Create genMakefile file for the template 152 mf, err := createMakeFile(path, makeFilename) 153 defer func() { 154 e = dir.CloseFile(mf, e) 155 }() 156 if err != nil { 157 return err 158 } 159 if mf != nil { 160 // Execute the template 161 err = t.Execute(mf, data) 162 } 163 return err 164 } 165 166 func getMbtPath(useDefaultMbt bool) string { 167 if useDefaultMbt { 168 return "mbt" 169 } 170 path, err := os.Executable() 171 // In case an error occurred we use default mbt 172 if err != nil { 173 return "mbt" 174 } 175 // If we're on windows the path with backslashes doesn't work with the makefile when running from shell 176 // (and it does work with slashes when running in windows cmd) 177 return shellquote.Join(filepath.ToSlash(path)) 178 } 179 180 func getExtensionsArg(extensions []string, makefileDirPath string, argName string) string { 181 if len(extensions) == 0 { 182 return "" 183 } 184 185 // We want to use a path relative to the makefile if possible, instead of an absolute path 186 relExtPaths := make([]string, len(extensions)) 187 for i, ext := range extensions { 188 relPath, err := filepath.Rel(makefileDirPath, ext) 189 if err != nil { 190 // Use the original path if the relative path can't be determined 191 relExtPaths[i] = ext 192 } else { 193 // Note: we can't use filepath.Join because it considers $(CURDIR) to be a path part so .. will remove it 194 relExtPaths[i] = "$(CURDIR)" + string(filepath.Separator) + relPath 195 } 196 } 197 return fmt.Sprintf(` %s="%s"`, argName, strings.Join(relExtPaths, ",")) 198 } 199 200 func mapTpl(templateContent []byte, BasePreContent []byte, BasePostContent []byte, useDefaultMbt bool, extensions []string, makefileDirPath string) (*template.Template, error) { 201 funcMap := template.FuncMap{ 202 "CommandProvider": func(modules mta.Module) (commands.CommandList, error) { 203 cmds, _, err := commands.CommandProvider(modules) 204 return cmds, err 205 }, 206 "Version": version.GetVersion, 207 "MbtPath": func() string { 208 return getMbtPath(useDefaultMbt) 209 }, 210 "ExtensionsArg": func(argName string) string { 211 return getExtensionsArg(extensions, makefileDirPath, argName) 212 }, 213 } 214 fullTemplate := append(baseArgs, BasePreContent...) 215 fullTemplate = append(fullTemplate, templateContent...) 216 fullTemplate = append(fullTemplate, BasePostContent...) 217 fullTemplateStr := string(fullTemplate) 218 // parse the template txt file 219 return template.New("makeTemplate").Funcs(funcMap).Parse(fullTemplateStr) 220 } 221 222 // IsVerboseMode returns true if the mode of the makefile template should be verbose 223 func IsVerboseMode(mode string) bool { 224 return (mode == "verbose") || (mode == "v") 225 } 226 227 // Get template (default/verbose) according to the CLI flags 228 func getTplCfg(mode string, isDep bool) (tplCfg, error) { 229 tpl := tplCfg{} 230 if IsVerboseMode(mode) { 231 tpl.tplContent = makeVerbose 232 tpl.preContent = basePreVerbose 233 tpl.postContent = basePost 234 } else if mode == "" { 235 tpl.tplContent = makeDefault 236 tpl.preContent = basePreDefault 237 tpl.postContent = basePost 238 } else { 239 return tplCfg{}, fmt.Errorf(cmdNotSupportedMsg, mode) 240 } 241 return tpl, nil 242 } 243 244 // Find string in arg slice 245 func stringInSlice(a string, list []string) bool { 246 for _, b := range list { 247 if b == a { 248 return true 249 } 250 } 251 return false 252 } 253 254 func createMakeFile(path, filename string) (file *os.File, err error) { 255 256 fullFilename := filepath.Join(path, filename) 257 var mf *os.File 258 if _, err = os.Stat(fullFilename); err == nil { 259 return nil, fmt.Errorf(genFailedOnFileCreationMsg, filename, fullFilename) 260 } 261 mf, err = dir.CreateFile(fullFilename) 262 return mf, err 263 }