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  }