// This file is part of arduino-cli. // // Copyright 2020 ARDUINO SA (http://www.arduino.cc/) // // This software is released under the GNU General Public License version 3, // which covers the main part of arduino-cli. // The terms of this license can be found at: // https://www.gnu.org/licenses/gpl-3.0.en.html // // You can be released from the requirements of the above licenses by purchasing // a commercial license. Buying such a license is mandatory if you want to // modify or otherwise use the software for commercial activities involving the // Arduino software without disclosing the source code of your own applications. // To purchase a commercial license, send an email to license@arduino.cc. package test import ( "encoding/json" "fmt" "io" "io/ioutil" "net/http" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" "github.com/arduino/arduino-cli/legacy/builder/constants" "github.com/arduino/arduino-cli/legacy/builder/gohasissues" "github.com/arduino/arduino-cli/legacy/builder/utils" "github.com/arduino/go-paths-helper" "github.com/arduino/go-properties-orderedmap" "github.com/pkg/errors" ) var hardwareFolder = paths.New("downloaded_hardware") var boardManagerFolder = paths.New("downloaded_board_manager_stuff") var toolsFolder = paths.New("downloaded_tools") var librariesFolder = paths.New("downloaded_libraries") var patchesFolder = paths.New("downloaded_stuff_patches") type Tool struct { Name string Package string Version string OsUrls []OsUrl } type OsUrl struct { Os string Url string } type Library struct { Name string Version string VersionInLibProperties string Url string } type Core struct { Maintainer string Arch string Version string Url string } func DownloadCoresAndToolsAndLibraries(t *testing.T) { cores := []Core{ Core{Maintainer: "arduino", Arch: "avr", Version: "1.6.10"}, Core{Maintainer: "arduino", Arch: "sam", Version: "1.6.7"}, } boardsManagerCores := []Core{ Core{Maintainer: "arduino", Arch: "samd", Version: "1.6.5"}, } boardsManagerRedBearCores := []Core{ Core{Maintainer: "RedBearLab", Arch: "avr", Version: "1.0.0", Url: "https://redbearlab.github.io/arduino/Blend/blend_boards.zip"}, } toolsMultipleVersions := []Tool{ Tool{Name: "bossac", Version: "1.6.1-arduino"}, Tool{Name: "bossac", Version: "1.5-arduino"}, } tools := []Tool{ Tool{Name: "avrdude", Version: "6.0.1-arduino5"}, Tool{Name: "avr-gcc", Version: "4.8.1-arduino5"}, Tool{Name: "arm-none-eabi-gcc", Version: "4.8.3-2014q1"}, Tool{Name: "ctags", Version: "5.8-arduino11", OsUrls: []OsUrl{ OsUrl{Os: "i686-pc-linux-gnu", Url: "http://downloads.arduino.cc/tools/ctags-5.8-arduino11-i686-pc-linux-gnu.tar.bz2"}, OsUrl{Os: "x86_64-pc-linux-gnu", Url: "http://downloads.arduino.cc/tools/ctags-5.8-arduino11-x86_64-pc-linux-gnu.tar.bz2"}, OsUrl{Os: "i686-mingw32", Url: "http://downloads.arduino.cc/tools/ctags-5.8-arduino11-i686-mingw32.zip"}, OsUrl{Os: "x86_64-apple-darwin", Url: "http://downloads.arduino.cc/tools/ctags-5.8-arduino11-x86_64-apple-darwin.zip"}, OsUrl{Os: "arm-linux-gnueabihf", Url: "http://downloads.arduino.cc/tools/ctags-5.8-arduino11-armv6-linux-gnueabihf.tar.bz2"}, OsUrl{Os: "aarch64-linux-gnu", Url: "http://downloads.arduino.cc/tools/ctags-5.8-arduino11-aarch64-linux-gnu.tar.bz2"}, }, }, Tool{Name: "arduino-preprocessor", Version: "0.1.5", OsUrls: []OsUrl{ OsUrl{Os: "i686-pc-linux-gnu", Url: "https://github.com/arduino/arduino-preprocessor/releases/download/0.1.5/arduino-preprocessor-0.1.5-i686-pc-linux-gnu.tar.bz2"}, OsUrl{Os: "x86_64-pc-linux-gnu", Url: "https://github.com/arduino/arduino-preprocessor/releases/download/0.1.5/arduino-preprocessor-0.1.5-x86_64-pc-linux-gnu.tar.bz2"}, OsUrl{Os: "i686-mingw32", Url: "https://github.com/arduino/arduino-preprocessor/releases/download/0.1.5/arduino-preprocessor-0.1.5-i686-w64-mingw32.tar.bz2"}, OsUrl{Os: "x86_64-apple-darwin", Url: "https://github.com/arduino/arduino-preprocessor/releases/download/0.1.5/arduino-preprocessor-0.1.5-x86_64-apple-darwin11.tar.bz2"}, OsUrl{Os: "arm-linux-gnueabihf", Url: "https://github.com/arduino/arduino-preprocessor/releases/download/0.1.5/arduino-preprocessor-0.1.5-arm-linux-gnueabihf.tar.bz2"}, OsUrl{Os: "aarch64-linux-gnu", Url: "https://github.com/arduino/arduino-preprocessor/releases/download/0.1.5/arduino-preprocessor-0.1.5-aarch64-linux-gnu.tar.bz2"}, }, }, } boardsManagerTools := []Tool{ Tool{Name: "openocd", Version: "0.9.0-arduino", Package: "arduino"}, Tool{Name: "CMSIS", Version: "4.0.0-atmel", Package: "arduino"}, } boardsManagerRFduinoTools := []Tool{ Tool{Name: "arm-none-eabi-gcc", Version: "4.8.3-2014q1", Package: "RFduino"}, } libraries := []Library{ Library{Name: "Audio", Version: "1.0.4"}, Library{Name: "Adafruit PN532", Version: "1.0.0"}, Library{Name: "Bridge", Version: "1.6.1"}, Library{Name: "CapacitiveSensor", Version: "0.5.0", VersionInLibProperties: "0.5"}, Library{Name: "Ethernet", Version: "1.1.1"}, Library{Name: "Robot IR Remote", Version: "2.0.0"}, Library{Name: "FastLED", Version: "3.1.0"}, } download(t, cores, boardsManagerCores, boardsManagerRedBearCores, tools, toolsMultipleVersions, boardsManagerTools, boardsManagerRFduinoTools, libraries) patchFiles(t) } func patchFiles(t *testing.T) { err := patchesFolder.MkdirAll() NoError(t, err) files, err := patchesFolder.ReadDir() NoError(t, err) for _, file := range files { if file.Ext() == ".patch" { panic("Patching for downloaded tools is not available! (see https://github.com/arduino/arduino-builder/issues/147)") // XXX: find an alternative to x/codereview/patch // https://github.com/arduino/arduino-builder/issues/147 /* data, err := ioutil.ReadFile(Abs(t, filepath.Join(PATCHES_FOLDER, file.Name()))) NoError(t, err) patchSet, err := patch.Parse(data) NoError(t, err) operations, err := patchSet.Apply(ioutil.ReadFile) for _, op := range operations { utils.WriteFileBytes(op.Dst, op.Data) } */ } } } func download(t *testing.T, cores, boardsManagerCores, boardsManagerRedBearCores []Core, tools, toolsMultipleVersions, boardsManagerTools, boardsManagerRFduinoTools []Tool, libraries []Library) { allCoresDownloaded, err := allCoresAlreadyDownloadedAndUnpacked(hardwareFolder, cores) NoError(t, err) if allCoresDownloaded && allBoardsManagerCoresAlreadyDownloadedAndUnpacked(boardManagerFolder, boardsManagerCores) && allBoardsManagerCoresAlreadyDownloadedAndUnpacked(boardManagerFolder, boardsManagerRedBearCores) && allBoardsManagerToolsAlreadyDownloadedAndUnpacked(boardManagerFolder, boardsManagerTools) && allBoardsManagerToolsAlreadyDownloadedAndUnpacked(boardManagerFolder, boardsManagerRFduinoTools) && allToolsAlreadyDownloadedAndUnpacked(toolsFolder, tools) && allToolsAlreadyDownloadedAndUnpacked(toolsFolder, toolsMultipleVersions) && allLibrariesAlreadyDownloadedAndUnpacked(librariesFolder, libraries) { return } index, err := downloadIndex("http://downloads.arduino.cc/packages/package_index.json") NoError(t, err) err = downloadCores(cores, index) NoError(t, err) err = downloadBoardManagerCores(boardsManagerCores, index) NoError(t, err) err = downloadTools(tools, index) NoError(t, err) err = downloadToolsMultipleVersions(toolsMultipleVersions, index) NoError(t, err) err = downloadBoardsManagerTools(boardsManagerTools, index) NoError(t, err) rfduinoIndex, err := downloadIndex("http://downloads.arduino.cc/packages/test_package_rfduino_index.json") NoError(t, err) err = downloadBoardsManagerTools(boardsManagerRFduinoTools, rfduinoIndex) NoError(t, err) err = downloadBoardManagerCores(boardsManagerRedBearCores, nil) NoError(t, err) librariesIndex, err := downloadIndex("http://downloads.arduino.cc/libraries/library_index.json") NoError(t, err) err = downloadLibraries(libraries, librariesIndex) NoError(t, err) } func downloadIndex(url string) (map[string]interface{}, error) { res, err := http.Get(url) if err != nil { return nil, err } bytes, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err } res.Body.Close() index := make(map[string]interface{}) err = json.Unmarshal(bytes, &index) if err != nil { return nil, err } return index, nil } func downloadCores(cores []Core, index map[string]interface{}) error { for _, core := range cores { url, err := findCoreUrl(index, core) if err != nil { return errors.WithStack(err) } err = downloadAndUnpackCore(core, url, hardwareFolder) if err != nil { return errors.WithStack(err) } } return nil } func downloadBoardManagerCores(cores []Core, index map[string]interface{}) error { for _, core := range cores { url, err := findCoreUrl(index, core) if err != nil { return errors.WithStack(err) } err = downloadAndUnpackBoardManagerCore(core, url, boardManagerFolder) if err != nil { return errors.WithStack(err) } } return nil } func findCoreUrl(index map[string]interface{}, core Core) (string, error) { if core.Url != "" { return core.Url, nil } packages := index["packages"].([]interface{}) for _, p := range packages { pack := p.(map[string]interface{}) if pack[constants.PACKAGE_NAME].(string) == core.Maintainer { packagePlatforms := pack["platforms"].([]interface{}) for _, pt := range packagePlatforms { packagePlatform := pt.(map[string]interface{}) if packagePlatform[constants.PLATFORM_ARCHITECTURE] == core.Arch && packagePlatform[constants.PLATFORM_VERSION] == core.Version { return packagePlatform[constants.PLATFORM_URL].(string), nil } } } } return constants.EMPTY_STRING, errors.Errorf("Unable to find tool " + core.Maintainer + " " + core.Arch + " " + core.Version) } func downloadTools(tools []Tool, index map[string]interface{}) error { host := translateGOOSGOARCHToPackageIndexValue() for _, tool := range tools { url, err := findToolUrl(index, tool, host) if err != nil { return errors.WithStack(err) } err = downloadAndUnpackTool(tool, url, toolsFolder, true) if err != nil { return errors.WithStack(err) } } return nil } func downloadToolsMultipleVersions(tools []Tool, index map[string]interface{}) error { host := translateGOOSGOARCHToPackageIndexValue() for _, tool := range tools { if !toolAlreadyDownloadedAndUnpacked(toolsFolder, tool) { if toolsFolder.Join(tool.Name).Exist() { if err := toolsFolder.Join(tool.Name).RemoveAll(); err != nil { return errors.WithStack(err) } } } } for _, tool := range tools { url, err := findToolUrl(index, tool, host) if err != nil { return errors.WithStack(err) } err = downloadAndUnpackTool(tool, url, toolsFolder, false) if err != nil { return errors.WithStack(err) } } return nil } func downloadBoardsManagerTools(tools []Tool, index map[string]interface{}) error { host := translateGOOSGOARCHToPackageIndexValue() for _, tool := range tools { url, err := findToolUrl(index, tool, host) if err != nil { return errors.WithStack(err) } err = downloadAndUnpackBoardsManagerTool(tool, url, boardManagerFolder) if err != nil { return errors.WithStack(err) } } return nil } func allBoardsManagerCoresAlreadyDownloadedAndUnpacked(targetPath *paths.Path, cores []Core) bool { for _, core := range cores { if !boardManagerCoreAlreadyDownloadedAndUnpacked(targetPath, core) { return false } } return true } func boardManagerCoreAlreadyDownloadedAndUnpacked(targetPath *paths.Path, core Core) bool { return targetPath.Join(core.Maintainer, "hardware", core.Arch, core.Version).Exist() } func allCoresAlreadyDownloadedAndUnpacked(targetPath *paths.Path, cores []Core) (bool, error) { for _, core := range cores { alreadyDownloaded, err := coreAlreadyDownloadedAndUnpacked(targetPath, core) if err != nil { return false, errors.WithStack(err) } if !alreadyDownloaded { return false, nil } } return true, nil } func coreAlreadyDownloadedAndUnpacked(targetPath *paths.Path, core Core) (bool, error) { corePath := targetPath.Join(core.Maintainer, core.Arch) if corePath.NotExist() { return false, nil } platform, err := properties.LoadFromPath(corePath.Join("platform.txt")) if err != nil { return false, errors.WithStack(err) } if core.Version != platform.Get("version") { err := corePath.RemoveAll() return false, errors.WithStack(err) } return true, nil } func allBoardsManagerToolsAlreadyDownloadedAndUnpacked(targetPath *paths.Path, tools []Tool) bool { for _, tool := range tools { if !boardManagerToolAlreadyDownloadedAndUnpacked(targetPath, tool) { return false } } return true } func boardManagerToolAlreadyDownloadedAndUnpacked(targetPath *paths.Path, tool Tool) bool { return targetPath.Join(tool.Package, constants.FOLDER_TOOLS, tool.Name, tool.Version).Exist() } func allToolsAlreadyDownloadedAndUnpacked(targetPath *paths.Path, tools []Tool) bool { for _, tool := range tools { if !toolAlreadyDownloadedAndUnpacked(targetPath, tool) { return false } } return true } func toolAlreadyDownloadedAndUnpacked(targetPath *paths.Path, tool Tool) bool { return targetPath.Join(tool.Name, tool.Version).Exist() } func allLibrariesAlreadyDownloadedAndUnpacked(targetPath *paths.Path, libraries []Library) bool { for _, library := range libraries { if !libraryAlreadyDownloadedAndUnpacked(targetPath, library) { return false } } return true } func libraryAlreadyDownloadedAndUnpacked(targetPath *paths.Path, library Library) bool { libPath := targetPath.Join(strings.Replace(library.Name, " ", "_", -1)) if !libPath.Exist() { return false } libProps, err := properties.LoadFromPath(libPath.Join("library.properties")) if err != nil { return false } return libProps.Get("version") == library.Version || libProps.Get("version") == library.VersionInLibProperties } func downloadAndUnpackCore(core Core, url string, targetPath *paths.Path) error { alreadyDownloaded, err := coreAlreadyDownloadedAndUnpacked(targetPath, core) if err != nil { return errors.WithStack(err) } if alreadyDownloaded { return nil } if err := targetPath.ToAbs(); err != nil { return errors.WithStack(err) } unpackFolder, files, err := downloadAndUnpack(url) if err != nil { return errors.WithStack(err) } defer unpackFolder.RemoveAll() packagerPath := targetPath.Join(core.Maintainer) corePath := targetPath.Join(core.Maintainer, core.Arch) if corePath.Exist() { if err := corePath.RemoveAll(); err != nil { return errors.WithStack(err) } } if len(files) == 1 && files[0].IsDir() { if err := packagerPath.MkdirAll(); err != nil { return errors.WithStack(err) } err = copyRecursive(unpackFolder.Join(files[0].Name()), targetPath.Join(core.Maintainer, core.Arch)) if err != nil { return errors.WithStack(err) } } else { if err := targetPath.Join(core.Maintainer, core.Arch).MkdirAll(); err != nil { return errors.WithStack(err) } for _, file := range files { err = copyRecursive(unpackFolder.Join(file.Name()), targetPath.Join(core.Maintainer, core.Arch, file.Name())) if err != nil { return errors.WithStack(err) } } } return nil } func downloadAndUnpackBoardManagerCore(core Core, url string, targetPath *paths.Path) error { if boardManagerCoreAlreadyDownloadedAndUnpacked(targetPath, core) { return nil } if err := targetPath.ToAbs(); err != nil { return errors.WithStack(err) } unpackFolder, files, err := downloadAndUnpack(url) if err != nil { return errors.WithStack(err) } defer unpackFolder.RemoveAll() corePath := targetPath.Join(core.Maintainer, "hardware", core.Arch) if corePath.Exist() { if err := corePath.RemoveAll(); err != nil { return errors.WithStack(err) } } if len(files) == 1 && files[0].IsDir() { if err := corePath.MkdirAll(); err != nil { return errors.WithStack(err) } err = copyRecursive(unpackFolder.Join(files[0].Name()), corePath.Join(core.Version)) if err != nil { return errors.WithStack(err) } } else { if err := corePath.Join(core.Version).MkdirAll(); err != nil { return errors.WithStack(err) } for _, file := range files { err = copyRecursive(unpackFolder.Join(file.Name()), corePath.Join(core.Version, file.Name())) if err != nil { return errors.WithStack(err) } } } return nil } func downloadAndUnpackBoardsManagerTool(tool Tool, url string, targetPath *paths.Path) error { if boardManagerToolAlreadyDownloadedAndUnpacked(targetPath, tool) { return nil } if err := targetPath.ToAbs(); err != nil { return errors.WithStack(err) } unpackFolder, files, err := downloadAndUnpack(url) if err != nil { return errors.WithStack(err) } defer unpackFolder.RemoveAll() if len(files) == 1 && files[0].IsDir() { if err := targetPath.Join(tool.Package, constants.FOLDER_TOOLS, tool.Name).MkdirAll(); err != nil { return errors.WithStack(err) } err = copyRecursive(unpackFolder.Join(files[0].Name()), targetPath.Join(tool.Package, constants.FOLDER_TOOLS, tool.Name, tool.Version)) if err != nil { return errors.WithStack(err) } } else { if err := targetPath.Join(tool.Package, constants.FOLDER_TOOLS, tool.Name, tool.Version).MkdirAll(); err != nil { return errors.WithStack(err) } for _, file := range files { err = copyRecursive(unpackFolder.Join(file.Name()), targetPath.Join(tool.Package, constants.FOLDER_TOOLS, tool.Name, tool.Version, file.Name())) if err != nil { return errors.WithStack(err) } } } return nil } func downloadAndUnpackTool(tool Tool, url string, targetPath *paths.Path, deleteIfMissing bool) error { if toolAlreadyDownloadedAndUnpacked(targetPath, tool) { return nil } if err := targetPath.ToAbs(); err != nil { return errors.WithStack(err) } unpackFolder, files, err := downloadAndUnpack(url) if err != nil { return errors.WithStack(err) } defer unpackFolder.RemoveAll() toolPath := targetPath.Join(tool.Name) if deleteIfMissing { if toolPath.Exist() { if err := toolPath.MkdirAll(); err != nil { return errors.WithStack(err) } } } if len(files) == 1 && files[0].IsDir() { if err := toolPath.MkdirAll(); err != nil { return errors.WithStack(err) } err = copyRecursive(unpackFolder.Join(files[0].Name()), toolPath.Join(tool.Version)) if err != nil { return errors.WithStack(err) } } else { if err := toolPath.Join(tool.Version).MkdirAll(); err != nil { return errors.WithStack(err) } for _, file := range files { err = copyRecursive(unpackFolder.Join(file.Name()), toolPath.Join(tool.Version, file.Name())) if err != nil { return errors.WithStack(err) } } } return nil } func downloadAndUnpack(url string) (*paths.Path, []os.FileInfo, error) { fmt.Fprintln(os.Stderr, "Downloading "+url) unpackFolder, err := paths.MkTempDir("", "arduino-builder-tool") if err != nil { return nil, nil, errors.WithStack(err) } urlParts := strings.Split(url, "/") archiveFileName := urlParts[len(urlParts)-1] archiveFilePath := unpackFolder.Join(archiveFileName) res, err := http.Get(url) if err != nil { return nil, nil, errors.WithStack(err) } bytes, err := ioutil.ReadAll(res.Body) if err != nil { return nil, nil, errors.WithStack(err) } res.Body.Close() archiveFilePath.WriteFile(bytes) cmd := buildUnpackCmd(archiveFilePath) out, err := cmd.CombinedOutput() if err != nil { fmt.Println(string(out)) return nil, nil, errors.WithStack(err) } if len(out) > 0 { fmt.Println(string(out)) } archiveFilePath.Remove() files, err := gohasissues.ReadDir(unpackFolder.String()) if err != nil { return nil, nil, errors.WithStack(err) } return unpackFolder, files, nil } func buildUnpackCmd(file *paths.Path) *exec.Cmd { var cmd *exec.Cmd if file.Ext() == ".zip" { cmd = exec.Command("unzip", "-qq", file.Base()) } else { cmd = exec.Command("tar", "xf", file.Base()) } cmd.Dir = file.Parent().String() return cmd } func translateGOOSGOARCHToPackageIndexValue() []string { switch value := runtime.GOOS + "-" + runtime.GOARCH; value { case "linux-amd64": return []string{"x86_64-pc-linux-gnu", "x86_64-linux-gnu"} case "linux-386": return []string{"i686-pc-linux-gnu", "i686-linux-gnu"} case "windows-amd64": return []string{"i686-mingw32", "i686-cygwin"} case "windows-386": return []string{"i686-mingw32", "i686-cygwin"} case "darwin-amd64": return []string{"i386-apple-darwin11", "x86_64-apple-darwin"} case "linux-arm": return []string{"arm-linux-gnueabihf"} default: panic("Unknown OS: " + value) } } func findToolUrl(index map[string]interface{}, tool Tool, host []string) (string, error) { if len(tool.OsUrls) > 0 { for _, osUrl := range tool.OsUrls { if utils.SliceContains(host, osUrl.Os) { return osUrl.Url, nil } } } else { packages := index["packages"].([]interface{}) for _, p := range packages { pack := p.(map[string]interface{}) packageTools := pack[constants.PACKAGE_TOOLS].([]interface{}) for _, pt := range packageTools { packageTool := pt.(map[string]interface{}) name := packageTool[constants.TOOL_NAME].(string) version := packageTool[constants.TOOL_VERSION].(string) if name == tool.Name && version == tool.Version { systems := packageTool["systems"].([]interface{}) for _, s := range systems { system := s.(map[string]interface{}) if utils.SliceContains(host, system["host"].(string)) { return system[constants.TOOL_URL].(string), nil } } } } } } return constants.EMPTY_STRING, errors.Errorf("Unable to find tool " + tool.Name + " " + tool.Version) } func downloadLibraries(libraries []Library, index map[string]interface{}) error { for _, library := range libraries { url, err := findLibraryUrl(index, library) if err != nil { return errors.WithStack(err) } err = downloadAndUnpackLibrary(library, url, librariesFolder) if err != nil { return errors.WithStack(err) } } return nil } func findLibraryUrl(index map[string]interface{}, library Library) (string, error) { if library.Url != "" { return library.Url, nil } libs := index["libraries"].([]interface{}) for _, l := range libs { lib := l.(map[string]interface{}) if library.Name == lib["name"].(string) && library.Version == lib["version"].(string) { return lib["url"].(string), nil } } return constants.EMPTY_STRING, errors.Errorf("Unable to find library " + library.Name + " " + library.Version) } func downloadAndUnpackLibrary(library Library, url string, targetPath *paths.Path) error { if libraryAlreadyDownloadedAndUnpacked(targetPath, library) { return nil } if err := targetPath.ToAbs(); err != nil { return errors.WithStack(err) } unpackFolder, files, err := downloadAndUnpack(url) if err != nil { return errors.WithStack(err) } defer unpackFolder.RemoveAll() libPath := targetPath.Join(strings.Replace(library.Name, " ", "_", -1)) if libPath.Exist() { if err := libPath.RemoveAll(); err != nil { return errors.WithStack(err) } } err = copyRecursive(unpackFolder.Join(files[0].Name()), libPath) if err != nil { return errors.WithStack(err) } return nil } func copyRecursive(from, to *paths.Path) error { copyFunc := func(currentPath string, info os.FileInfo, err error) error { if err != nil { return err } rel, err := filepath.Rel(from.String(), currentPath) if err != nil { return errors.WithStack(err) } targetPath := filepath.Join(to.String(), rel) if info.IsDir() { err := os.MkdirAll(targetPath, info.Mode()) if err != nil { return errors.WithStack(err) } } else if info.Mode().IsRegular() { fromFile, err := os.Open(currentPath) if err != nil { return errors.WithStack(err) } defer fromFile.Close() targetFile, err := os.Create(targetPath) if err != nil { return errors.WithStack(err) } defer targetFile.Close() _, err = io.Copy(targetFile, fromFile) if err != nil { return errors.WithStack(err) } err = os.Chmod(targetPath, info.Mode()) if err != nil { return errors.WithStack(err) } } else if info.Mode()&os.ModeSymlink == os.ModeSymlink { linkedFile, err := os.Readlink(currentPath) if err != nil { return errors.WithStack(err) } fromFile := filepath.Join(filepath.Dir(targetPath), linkedFile) err = os.Symlink(fromFile, targetPath) if err != nil { return errors.WithStack(err) } } else { return errors.Errorf("unable to copy file " + currentPath) } return nil } err := gohasissues.Walk(from.String(), copyFunc) return errors.WithStack(err) }