Skip to content

Commit 3c000e1

Browse files
committed
Add archive command to zip a sketch and its files
1 parent ef57e49 commit 3c000e1

21 files changed

+6162
-315
lines changed

Diff for: cli/archive/archive.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package archive
17+
18+
import (
19+
"context"
20+
"os"
21+
22+
"github.com/arduino/arduino-cli/cli/errorcodes"
23+
"github.com/arduino/arduino-cli/cli/feedback"
24+
"github.com/arduino/arduino-cli/commands"
25+
rpc "github.com/arduino/arduino-cli/rpc/commands"
26+
"github.com/sirupsen/logrus"
27+
"github.com/spf13/cobra"
28+
)
29+
30+
var includeBuildDir bool
31+
32+
// NewCommand creates a new `archive` command
33+
func NewCommand() *cobra.Command {
34+
command := &cobra.Command{
35+
Use: "archive <sketchPath> <archivePath>",
36+
Short: "Creates a zip file containing all sketch files.",
37+
Long: "Creates a zip file containing all sketch files.",
38+
Example: "" +
39+
" " + os.Args[0] + " archive\n" +
40+
" " + os.Args[0] + " archive .\n" +
41+
" " + os.Args[0] + " archive . MySketchArchive.zip\n" +
42+
" " + os.Args[0] + " archive /home/user/Arduino/MySketch\n" +
43+
" " + os.Args[0] + " archive /home/user/Arduino/MySketch /home/user/MySketchArchive.zip",
44+
Args: cobra.MaximumNArgs(2),
45+
Run: runArchiveCommand,
46+
}
47+
48+
command.Flags().BoolVar(&includeBuildDir, "include-build-dir", false, "Includes build directory in the archive.")
49+
50+
return command
51+
}
52+
53+
func runArchiveCommand(cmd *cobra.Command, args []string) {
54+
logrus.Info("Executing `arduino archive`")
55+
56+
sketchPath := ""
57+
if len(args) >= 1 {
58+
sketchPath = args[0]
59+
}
60+
61+
archivePath := ""
62+
if len(args) == 2 {
63+
archivePath = args[1]
64+
}
65+
66+
_, err := commands.ArchiveSketch(context.Background(),
67+
&rpc.ArchiveSketchReq{
68+
SketchPath: sketchPath,
69+
ArchivePath: archivePath,
70+
IncludeBuildDir: includeBuildDir,
71+
})
72+
73+
if err != nil {
74+
feedback.Errorf("Error archiving: %v", err)
75+
os.Exit(errorcodes.ErrGeneric)
76+
}
77+
}

Diff for: cli/cli.go

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"os"
2222
"strings"
2323

24+
"github.com/arduino/arduino-cli/cli/archive"
2425
"github.com/arduino/arduino-cli/cli/board"
2526
"github.com/arduino/arduino-cli/cli/burnbootloader"
2627
"github.com/arduino/arduino-cli/cli/cache"
@@ -79,6 +80,7 @@ func NewCommand() *cobra.Command {
7980

8081
// this is here only for testing
8182
func createCliCommandTree(cmd *cobra.Command) {
83+
cmd.AddCommand(archive.NewCommand())
8284
cmd.AddCommand(board.NewCommand())
8385
cmd.AddCommand(cache.NewCommand())
8486
cmd.AddCommand(compile.NewCommand())

Diff for: commands/daemon/daemon.go

+5
Original file line numberDiff line numberDiff line change
@@ -337,3 +337,8 @@ func (s *ArduinoCoreServerImpl) LibrarySearch(ctx context.Context, req *rpc.Libr
337337
func (s *ArduinoCoreServerImpl) LibraryList(ctx context.Context, req *rpc.LibraryListReq) (*rpc.LibraryListResp, error) {
338338
return lib.LibraryList(ctx, req)
339339
}
340+
341+
// ArchiveSketch FIXMEDOC
342+
func (s *ArduinoCoreServerImpl) ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.ArchiveSketchResp, error) {
343+
return commands.ArchiveSketch(ctx, req)
344+
}

Diff for: commands/instances.go

+152
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@
1616
package commands
1717

1818
import (
19+
"archive/zip"
1920
"context"
2021
"errors"
2122
"fmt"
23+
"io"
2224
"io/ioutil"
2325
"net/url"
26+
"os"
2427
"path"
28+
"strings"
2529

2630
"github.com/arduino/arduino-cli/arduino/builder"
2731
"github.com/arduino/arduino-cli/arduino/cores"
@@ -702,3 +706,151 @@ func LoadSketch(ctx context.Context, req *rpc.LoadSketchReq) (*rpc.LoadSketchRes
702706
AdditionalFiles: additionalFiles,
703707
}, nil
704708
}
709+
710+
// ArchiveSketch FIXMEDOC
711+
func ArchiveSketch(ctx context.Context, req *rpc.ArchiveSketchReq) (*rpc.ArchiveSketchResp, error) {
712+
// sketchName is the name of the sketch without extension, for example "MySketch"
713+
var sketchName string
714+
715+
sketchPath := paths.New(req.SketchPath)
716+
if sketchPath == nil {
717+
sketchPath = paths.New(".")
718+
}
719+
720+
sketchPath, err := sketchPath.Clean().Abs()
721+
if err != nil {
722+
return nil, fmt.Errorf("Error getting absolute sketch path %v", err)
723+
}
724+
725+
// Get the sketch name and make sketchPath point to the ino file
726+
if sketchPath.IsDir() {
727+
sketchName = sketchPath.Base()
728+
sketchPath = sketchPath.Join(sketchName + ".ino")
729+
} else if sketchPath.Ext() == ".ino" {
730+
sketchName = strings.TrimSuffix(sketchPath.Base(), ".ino")
731+
}
732+
733+
// Checks if it's really a sketch
734+
if sketchPath.NotExist() {
735+
return nil, fmt.Errorf("specified path is not a sketch: %v", sketchPath.String())
736+
}
737+
738+
archivePath := paths.New(req.ArchivePath)
739+
if archivePath == nil {
740+
archivePath = sketchPath.Parent().Parent()
741+
}
742+
743+
archivePath, err = archivePath.Clean().Abs()
744+
if err != nil {
745+
return nil, fmt.Errorf("Error getting absolute archive path %v", err)
746+
}
747+
748+
// Makes archivePath point to a zip file
749+
if archivePath.IsDir() {
750+
archivePath = archivePath.Join(sketchName + ".zip")
751+
} else if archivePath.Ext() == "" {
752+
archivePath = paths.New(archivePath.String() + ".zip")
753+
}
754+
755+
if archivePath.Exist() {
756+
return nil, fmt.Errorf("archive already exists")
757+
}
758+
759+
archive, err := os.Create(archivePath.Clean().String())
760+
if err != nil {
761+
return nil, fmt.Errorf("Error creating archive: %v", err)
762+
}
763+
defer archive.Close()
764+
765+
zipWriter := zip.NewWriter(archive)
766+
defer zipWriter.Close()
767+
768+
filesToZip, err := getSketchContent(sketchPath.Parent())
769+
if err != nil {
770+
return nil, fmt.Errorf("Error retrieving sketch files: %v", err)
771+
}
772+
773+
for _, f := range filesToZip {
774+
775+
if !req.IncludeBuildDir {
776+
filePath, err := sketchPath.Parent().Parent().RelTo(f)
777+
if err != nil {
778+
return nil, fmt.Errorf("Error calculating relative file path: %v", err)
779+
}
780+
781+
// Skips build folder
782+
if strings.HasPrefix(filePath.String(), sketchName+"/build") {
783+
continue
784+
}
785+
}
786+
787+
// We get the parent path since we want the archive to unpack as a folder.
788+
// If we don't do this the archive would contain all the sketch files as top level.
789+
err = addFileToSketchArchive(zipWriter, f, sketchPath.Parent().Parent())
790+
if err != nil {
791+
return nil, fmt.Errorf("Error adding file to archive: %v", err)
792+
}
793+
}
794+
795+
return &rpc.ArchiveSketchResp{}, nil
796+
}
797+
798+
// Recursively retrieves all files in the sketch folder
799+
func getSketchContent(sketchFolder *paths.Path) (paths.PathList, error) {
800+
sketchFiles, err := sketchFolder.ReadDir()
801+
if err != nil {
802+
return nil, err
803+
}
804+
for _, f := range sketchFiles {
805+
if f.IsDir() {
806+
files, err := getSketchContent(f)
807+
if err != nil {
808+
return nil, err
809+
}
810+
811+
sketchFiles = append(sketchFiles, files...)
812+
}
813+
}
814+
finalFiles := paths.PathList{}
815+
for _, f := range sketchFiles {
816+
if f.IsNotDir() {
817+
finalFiles = append(finalFiles, f)
818+
}
819+
}
820+
return finalFiles, nil
821+
}
822+
823+
// Adds a single file to an existing zip file
824+
func addFileToSketchArchive(zipWriter *zip.Writer, filePath, sketchPath *paths.Path) error {
825+
f, err := filePath.Open()
826+
if err != nil {
827+
return err
828+
}
829+
defer f.Close()
830+
831+
info, err := f.Stat()
832+
if err != nil {
833+
return err
834+
}
835+
836+
header, err := zip.FileInfoHeader(info)
837+
if err != nil {
838+
return err
839+
}
840+
841+
filePath, err = sketchPath.RelTo(filePath)
842+
if err != nil {
843+
return err
844+
}
845+
846+
header.Name = filePath.String()
847+
header.Method = zip.Deflate
848+
849+
writer, err := zipWriter.CreateHeader(header)
850+
if err != nil {
851+
return err
852+
}
853+
854+
_, err = io.Copy(writer, f)
855+
return err
856+
}

0 commit comments

Comments
 (0)