Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support build dependencies between modules in verbose makefile #551

Merged
merged 7 commits into from
Nov 10, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var validateCmdExclude string
func init() {

// Add command to the root
rootCmd.AddCommand(initCmd, buildCmd, validateCmd, cleanupCmd, provideCmd, generateCmd, moduleCmd, assembleCommand, projectCmd, mergeCmd, executeCommand)
rootCmd.AddCommand(initCmd, buildCmd, validateCmd, cleanupCmd, provideCmd, generateCmd, moduleCmd, assembleCommand, projectCmd, mergeCmd, executeCommand, copyCmd)
// Build module
provideCmd.AddCommand(provideModuleCmd)
// generate immutable commands
Expand Down
29 changes: 29 additions & 0 deletions cmd/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ package commands
import (
"github.com/spf13/cobra"

"github.com/SAP/cloud-mta-build-tool/internal/archive"
"github.com/SAP/cloud-mta-build-tool/internal/exec"
)

var executeCmdCommands []string
var executeCmdTimeout string
var copyCmdSrc string
var copyCmdTrg string
var copyCmdPatterns []string

// Execute commands in the current working directory with a timeout.
// This is used in verbose make files for implementing a timeout on module builds.
Expand All @@ -26,10 +30,35 @@ var executeCommand = &cobra.Command{
SilenceErrors: true,
}

// Copy files matching the specified patterns from the source path to the target path.
// This is used in verbose make files for copying artifacts from a module's dependencies before building the module.
var copyCmd = &cobra.Command{
Use: "cp",
Short: "Copy files by patterns",
Long: "Copy files by patterns",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
err := dir.CopyByPatterns(copyCmdSrc, copyCmdTrg, copyCmdPatterns)
logError(err)
return err
},
SilenceUsage: true,
Hidden: true,
SilenceErrors: true,
}

func init() {
// set flag of execute command
executeCommand.Flags().StringArrayVarP(&executeCmdCommands,
"commands", "c", nil, "commands to run")
executeCommand.Flags().StringVarP(&executeCmdTimeout,
"timeout", "t", "", "the timeout after which the run stops, in the format [123h][123m][123s]; 10m is set as the default")

// set flags of copy command
copyCmd.Flags().StringVarP(&copyCmdSrc, "source", "s", "",
"the path to the source folder")
copyCmd.Flags().StringVarP(&copyCmdTrg, "target", "t", "",
"the path to the target folder")
copyCmd.Flags().StringArrayVarP(&copyCmdPatterns,
"patterns", "p", nil, "patterns for matching the files and folders to copy")
}
93 changes: 93 additions & 0 deletions cmd/exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package commands

import (
"fmt"
"os"
"path/filepath"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

"github.com/SAP/cloud-mta-build-tool/internal/archive"
)

var _ = Describe("cp command", func() {
BeforeEach(func() {
copyCmdSrc = getTestPath("mtahtml5")
copyCmdTrg = getTestPath("result")
copyCmdPatterns = []string{}
allaVolkov marked this conversation as resolved.
Show resolved Hide resolved
err := dir.CreateDirIfNotExist(copyCmdTrg)
Ω(err).Should(Succeed())
})

AfterEach(func() {
Ω(os.RemoveAll(getTestPath("result"))).Should(Succeed())
})

It("copy - Sanity", func() {
copyCmdPatterns = []string{"mta.*", "ui5app2"}
Ω(copyCmd.RunE(nil, []string{})).Should(Succeed())
validateFilesInDir(getTestPath("result"), []string{"mta.sh", "mta.yaml", "ui5app2/", "ui5app2/test.txt"})
})
It("copy should not fail when the pattern is valid but doesn't match any files", func() {
copyCmdPatterns = []string{"xxx.xxx"}
Ω(copyCmd.RunE(nil, []string{})).Should(Succeed())
validateFilesInDir(getTestPath("result"), []string{})
})
It("copy should not fail when there are no patterns", func() {
copyCmdPatterns = []string{}
Ω(copyCmd.RunE(nil, []string{})).Should(Succeed())
validateFilesInDir(getTestPath("result"), []string{})
})
It("copy should return error when the source folder doesn't exist", func() {
copyCmdSrc = getTestPath("mtahtml6")
copyCmdPatterns = []string{"*"}
Ω(copyCmd.RunE(nil, []string{})).Should(HaveOccurred())
})
It("copy should return error when a pattern is invalid", func() {
copyCmdPatterns = []string{"["}
Ω(copyCmd.RunE(nil, []string{})).Should(HaveOccurred())
})
})

// Check the folder exists and includes exactly the expected files
func validateFilesInDir(src string, expectedFilesInDir []string) {
// List all files in the folder recursively
filesInDir := make([]string, 0)
err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// Don't include the folder itself
if filepath.Clean(path) == filepath.Clean(src) {
return nil
}
relPath, err := filepath.Rel(src, path)
if err != nil {
return err
}
relPath = filepath.ToSlash(relPath)
if info.IsDir() {
relPath += "/"
}
filesInDir = append(filesInDir, relPath)
return nil
})
Ω(err).Should(Succeed())

for _, expectedFile := range expectedFilesInDir {
Ω(contains(expectedFile, filesInDir)).Should(BeTrue(), fmt.Sprintf("expected %s to be in the directory; directory contains %v", expectedFile, filesInDir))
}
for _, existingFile := range filesInDir {
Ω(contains(existingFile, expectedFilesInDir)).Should(BeTrue(), fmt.Sprintf("did not expect %s to be in the directory; directory contains %v", existingFile, filesInDir))
}
}

func contains(element string, elements []string) bool {
for _, el := range elements {
if el == element {
return true
}
}
return false
}
72 changes: 69 additions & 3 deletions integration/cloud_mta_build_tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

dir "github.com/SAP/cloud-mta-build-tool/internal/archive"
"github.com/SAP/cloud-mta/mta"
)

Expand Down Expand Up @@ -374,6 +375,36 @@ modules:
Ω(filepath.Join(dir, "testdata", "mta_demo", "mta_archives", demoArchiveName)).Should(BeAnExistingFile())
})

It("Generate Verbose Makefile with module dependencies", func() {
dir, _ := os.Getwd()
path := filepath.Join(dir, "testdata", "moduledep")
archivePath := filepath.Join(path, "mta_archives", "f1_0.0.1.mtar")
tempZipPath := filepath.Join(path, "mta_archives", "data.zip")

Ω(os.RemoveAll(filepath.Join(path, "Makefile.mta"))).Should(Succeed())
Ω(os.RemoveAll(archivePath)).Should(Succeed())
Ω(os.RemoveAll(filepath.Join(path, "public", "client"))).Should(Succeed())
Ω(os.RemoveAll(tempZipPath)).Should(Succeed())

bin := filepath.FromSlash(binPath)
cmdOut, errString, _ := execute(bin, "init -m=verbose", path)
Ω(errString).Should(Equal(""))
Ω(cmdOut).ShouldNot(BeNil())
// Check the MakeFile was generated
Ω(filepath.Join(path, "Makefile.mta")).Should(BeAnExistingFile())

// Generate mtar
bin = filepath.FromSlash("make")
execute(bin, "-f Makefile.mta p=cf", path)
// Check the archive was generated
Ω(archivePath).Should(BeAnExistingFile())
validateMtaArchiveContents([]string{"module_with_dep/", "module_with_dep/data.zip"}, archivePath)

// Extract data.zip and check its content
err := extractFileFromZip(archivePath, "module_with_dep/data.zip", tempZipPath)
Ω(err).Should(Succeed())
validateArchiveContents([]string{"package.json", "client/", "client/client_package.json"}, tempZipPath)
})
})

var _ = Describe("MBT gen commands", func() {
Expand Down Expand Up @@ -586,13 +617,48 @@ func getFileContentFromZip(path string, filename string) ([]byte, error) {
return nil, fmt.Errorf(`file "%s" not found`, filename)
}

func extractFileFromZip(archivePath string, filename string, dst string) (error) {
zipFile, err := zip.OpenReader(archivePath)
if err != nil {
return err
}
defer func() {
_ = zipFile.Close()
}()
var fileToExtract *zip.File = nil
for _, file := range zipFile.File {
if strings.Contains(file.Name, filename) {
fileToExtract = file
}
}
if fileToExtract == nil {
return fmt.Errorf(`file "%s" not found`, filename)
}

in, err := fileToExtract.Open()
if err != nil {
return err
}
defer func() {
_ = in.Close()
}()
err = dir.WriteFile(in, dst)
return err
}

func validateMtaArchiveContents(expectedAdditionalFilesInArchive []string, archiveLocation string) {
expectedFilesInArchive := append(expectedAdditionalFilesInArchive, "META-INF/", "META-INF/MANIFEST.MF", "META-INF/mtad.yaml")
archiveReader, err := zip.OpenReader(archiveLocation)
validateArchiveContents(expectedFilesInArchive, archiveLocation)
}

func validateArchiveContents(expectedFilesInArchive []string, archiveLocation string) {
archive, err := zip.OpenReader(archiveLocation)
Ω(err).Should(Succeed())
defer archiveReader.Close()
defer func() {
_ = archive.Close()
}()
var filesInArchive []string
for _, file := range archiveReader.File {
for _, file := range archive.File {
filesInArchive = append(filesInArchive, file.Name)
}
for _, expectedFile := range expectedFilesInArchive {
Expand Down
34 changes: 34 additions & 0 deletions integration/testdata/moduledep/client/client_package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "f1.client",
"title": "f1",
"version": "0.0.1",
"description": "",
"scripts": {
"postinstall": "node ./scripts/bundle.js && npm run lint",
"lint": "node ./node_modules/eslint/bin/eslint . --ext=.js,.json"
},
"devDependencies": {
"eslint": "4.12.1",
"@sap/eslint-plugin-webide-feature": "1.3.15",
"@sap-webide/webide-client-tools": "latest"
},
"bundledPlugins": {
"p1": "file:src/p1"
},
"files": [
"i18n",
"src",
"config-preload.js",
"config-preload.json",
"package.json"
],
"webidePreloads": {
"js": [
"config-preload.js",
"i18n/config-preload.js"
],
"config": [
"config-preload.json"
]
}
}
21 changes: 21 additions & 0 deletions integration/testdata/moduledep/mta.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
_schema-version: "2.0.0"
ID: f1
version: 0.0.1

modules:
- name: module_with_dep
type: html5
path: public
build-parameters:
builder: zip
requires:
- name: m2
artifacts: ["*"]
target-path: "client"
- name: m2
type: html5
path: client
build-parameters:
builder: zip
supported-platforms: []

10 changes: 10 additions & 0 deletions integration/testdata/moduledep/public/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "f1.approuter",
"version": "0.0.1",
"scripts": {
"start": "node node_modules/@sap/approuter/approuter.js"
},
"dependencies": {
"@sap/approuter": "2.7.0"
}
}
1 change: 1 addition & 0 deletions internal/archive/archive_msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const (
copyByPatternFailedOnCreateMsg = `could not copy files matching the patterns [%s,...] from the "%s" folder to the "%s" folder: could not create the "%s" folder`
copyByPatternFailedOnTargetMsg = `could not copy files matching the patterns [%s,...] from the "%s" folder to the "%s" folder: "%s" is not a folder`
copyByPatternFailedOnMatchMsg = `could not copy files matching the "%s" pattern from the "%s" folder to the "%s": could not get list of files matching the "%s" pattern`
wrongPathMsg = `could not find the "%s" path`

// InitLocFailedOnWorkDirMsg - message raised on getting working directory when initializing location
InitLocFailedOnWorkDirMsg = `could not get working directory`
Expand Down
36 changes: 30 additions & 6 deletions internal/archive/fsops.go
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,19 @@ func CopyDir(src string, dst string, withParents bool, copyDirEntries func(entri
return copyDirEntries(entries, src, dst)
}

// FindPath returns the path or its first match in case it's a pattern. If the path doesn't exist an error is returned.
func FindPath(pathOrPattern string) (string, error) {
path := pathOrPattern
allaVolkov marked this conversation as resolved.
Show resolved Hide resolved
sourceEntries, err := filepath.Glob(path)
if err == nil && len(sourceEntries) > 0 {
return sourceEntries[0], nil
}
if err != nil {
return "", errors.Wrapf(err, wrongPathMsg, path)
}
return "", errors.Errorf(wrongPathMsg, path)
}

// CopyByPatterns - copy files/directories according to patterns
// from source folder to target folder
// patterns are relative to source folder
Expand All @@ -381,6 +394,12 @@ func CopyByPatterns(source, target string, patterns []string) error {

logs.Logger.Infof(copyByPatternMsg, patterns[0], source, target)

// Resolve the source pattern if necessary
source, err := FindPath(source)
if err != nil {
return err
}

infoTargetDir, err := os.Stat(target)
if err != nil {
err = CreateDirIfNotExist(target)
Expand Down Expand Up @@ -557,6 +576,17 @@ func CopyFile(src, dst string) (rerr error) {
rerr = CloseFile(in, rerr)
}()

err = WriteFile(in, dst)
if err != nil {
return err
}
err = changeTargetMode(src, dst)
allaVolkov marked this conversation as resolved.
Show resolved Hide resolved

return err

}

func WriteFile(in io.Reader, dst string) (rerr error) {
out, err := os.Create(dst)
if err != nil {
return err
Expand All @@ -566,13 +596,7 @@ func CopyFile(src, dst string) (rerr error) {
}()

_, err = io.Copy(out, in)
if err != nil {
return err
}
err = changeTargetMode(src, dst)

return err

}

func changeTargetMode(source, target string) error {
Expand Down
Loading