Skip to content

Add archive command to zip a sketch and its files #931

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

Merged
merged 3 commits into from
Sep 3, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
77 changes: 77 additions & 0 deletions cli/archive/archive.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// 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 [email protected].

package archive

import (
"context"
"os"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/commands"
rpc "github.com/arduino/arduino-cli/rpc/commands"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

var includeBuildDir bool

// NewCommand creates a new `archive` command
func NewCommand() *cobra.Command {
command := &cobra.Command{
Use: "archive <sketchPath> <archivePath>",
Short: "Creates a zip file containing all sketch files.",
Long: "Creates a zip file containing all sketch files.",
Example: "" +
" " + os.Args[0] + " archive\n" +
" " + os.Args[0] + " archive .\n" +
" " + os.Args[0] + " archive . MySketchArchive.zip\n" +
" " + os.Args[0] + " archive /home/user/Arduino/MySketch\n" +
" " + os.Args[0] + " archive /home/user/Arduino/MySketch /home/user/MySketchArchive.zip",
Args: cobra.MaximumNArgs(2),
Run: runArchiveCommand,
}

command.Flags().BoolVar(&includeBuildDir, "include-build-dir", false, "Includes build directory in the archive.")

return command
}

func runArchiveCommand(cmd *cobra.Command, args []string) {
logrus.Info("Executing `arduino archive`")

sketchPath := ""
if len(args) >= 1 {
sketchPath = args[0]
}

archivePath := ""
if len(args) == 2 {
archivePath = args[1]
}

_, err := commands.ArchiveSketch(context.Background(),
&rpc.ArchiveSketchReq{
SketchPath: sketchPath,
ArchivePath: archivePath,
IncludeBuildDir: includeBuildDir,
})

if err != nil {
feedback.Errorf("Error archiving: %v", err)
os.Exit(errorcodes.ErrGeneric)
}
}
2 changes: 2 additions & 0 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os"
"strings"

"github.com/arduino/arduino-cli/cli/archive"
"github.com/arduino/arduino-cli/cli/board"
"github.com/arduino/arduino-cli/cli/burnbootloader"
"github.com/arduino/arduino-cli/cli/cache"
Expand Down Expand Up @@ -79,6 +80,7 @@ func NewCommand() *cobra.Command {

// this is here only for testing
func createCliCommandTree(cmd *cobra.Command) {
cmd.AddCommand(archive.NewCommand())
cmd.AddCommand(board.NewCommand())
cmd.AddCommand(cache.NewCommand())
cmd.AddCommand(compile.NewCommand())
Expand Down
5 changes: 5 additions & 0 deletions commands/daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,3 +337,8 @@ func (s *ArduinoCoreServerImpl) LibrarySearch(ctx context.Context, req *rpc.Libr
func (s *ArduinoCoreServerImpl) LibraryList(ctx context.Context, req *rpc.LibraryListReq) (*rpc.LibraryListResp, error) {
return lib.LibraryList(ctx, req)
}

// ArchiveSketch FIXMEDOC
func (s *ArduinoCoreServerImpl) ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.ArchiveSketchResp, error) {
return commands.ArchiveSketch(ctx, req)
}
153 changes: 153 additions & 0 deletions commands/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@
package commands

import (
"archive/zip"
"context"
"errors"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path"
"path/filepath"
"strings"

"github.com/arduino/arduino-cli/arduino/builder"
"github.com/arduino/arduino-cli/arduino/cores"
Expand Down Expand Up @@ -702,3 +707,151 @@ func LoadSketch(ctx context.Context, req *rpc.LoadSketchReq) (*rpc.LoadSketchRes
AdditionalFiles: additionalFiles,
}, nil
}

// ArchiveSketch FIXMEDOC
func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.ArchiveSketchResp, error) {
// sketchName is the name of the sketch without extension, for example "MySketch"
var sketchName string

sketchPath := paths.New(req.SketchPath)
if sketchPath == nil {
sketchPath = paths.New(".")
}

sketchPath, err := sketchPath.Clean().Abs()
if err != nil {
return nil, fmt.Errorf("Error getting absolute sketch path %v", err)
}

// Get the sketch name and make sketchPath point to the ino file
if sketchPath.IsDir() {
sketchName = sketchPath.Base()
sketchPath = sketchPath.Join(sketchName + ".ino")
} else if sketchPath.Ext() == ".ino" {
sketchName = strings.TrimSuffix(sketchPath.Base(), ".ino")
}

// Checks if it's really a sketch
if sketchPath.NotExist() {
return nil, fmt.Errorf("specified path is not a sketch: %v", sketchPath.String())
}

archivePath := paths.New(req.ArchivePath)
if archivePath == nil {
archivePath = sketchPath.Parent().Parent()
}

archivePath, err = archivePath.Clean().Abs()
if err != nil {
return nil, fmt.Errorf("Error getting absolute archive path %v", err)
}

// Makes archivePath point to a zip file
if archivePath.IsDir() {
archivePath = archivePath.Join(sketchName + ".zip")
} else if archivePath.Ext() == "" {
archivePath = paths.New(archivePath.String() + ".zip")
}

if archivePath.Exist() {
return nil, fmt.Errorf("archive already exists")
}

archive, err := os.Create(archivePath.Clean().String())
if err != nil {
return nil, fmt.Errorf("Error creating archive: %v", err)
}
defer archive.Close()

zipWriter := zip.NewWriter(archive)
defer zipWriter.Close()

filesToZip, err := getSketchContent(sketchPath.Parent())
if err != nil {
return nil, fmt.Errorf("Error retrieving sketch files: %v", err)
}

for _, f := range filesToZip {

if !req.IncludeBuildDir {
filePath, err := sketchPath.Parent().Parent().RelTo(f)
if err != nil {
return nil, fmt.Errorf("Error calculating relative file path: %v", err)
}

// Skips build folder
if strings.HasPrefix(filePath.String(), sketchName+string(filepath.Separator)+"build") {
continue
}
}

// We get the parent path since we want the archive to unpack as a folder.
// If we don't do this the archive would contain all the sketch files as top level.
err = addFileToSketchArchive(zipWriter, f, sketchPath.Parent().Parent())
if err != nil {
return nil, fmt.Errorf("Error adding file to archive: %v", err)
}
}

return &rpc.ArchiveSketchResp{}, nil
}

// Recursively retrieves all files in the sketch folder
func getSketchContent(sketchFolder *paths.Path) (paths.PathList, error) {
sketchFiles, err := sketchFolder.ReadDir()
if err != nil {
return nil, err
}
for _, f := range sketchFiles {
if f.IsDir() {
files, err := getSketchContent(f)
if err != nil {
return nil, err
}

sketchFiles = append(sketchFiles, files...)
}
}
finalFiles := paths.PathList{}
for _, f := range sketchFiles {
if f.IsNotDir() {
finalFiles = append(finalFiles, f)
}
}
return finalFiles, nil
}

// Adds a single file to an existing zip file
func addFileToSketchArchive(zipWriter *zip.Writer, filePath, sketchPath *paths.Path) error {
f, err := filePath.Open()
if err != nil {
return err
}
defer f.Close()

info, err := f.Stat()
if err != nil {
return err
}

header, err := zip.FileInfoHeader(info)
if err != nil {
return err
}

filePath, err = sketchPath.RelTo(filePath)
if err != nil {
return err
}

header.Name = filePath.String()
header.Method = zip.Deflate

writer, err := zipWriter.CreateHeader(header)
if err != nil {
return err
}

_, err = io.Copy(writer, f)
return err
}
Loading