Skip to content

Commit c0a1d4f

Browse files
authored
Merge pull request #265 from arduino/massi/sketch
Turn Sketch commands into builder API functions
2 parents b3c28ff + 1e25134 commit c0a1d4f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+704
-418
lines changed

Diff for: arduino/builder/sketch.go

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2019 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 modify or
12+
// otherwise use the software for commercial activities involving the Arduino
13+
// software without disclosing the source code of your own applications. To purchase
14+
// a commercial license, send an email to [email protected].
15+
16+
package builder
17+
18+
import (
19+
"io/ioutil"
20+
"os"
21+
"path/filepath"
22+
"regexp"
23+
"strings"
24+
25+
"github.com/arduino/arduino-cli/arduino/globals"
26+
"github.com/arduino/arduino-cli/arduino/sketch"
27+
28+
"github.com/pkg/errors"
29+
)
30+
31+
var includesArduinoH = regexp.MustCompile(`(?m)^\s*#\s*include\s*[<\"]Arduino\.h[>\"]`)
32+
33+
// QuoteCppString returns the given string as a quoted string for use with the C
34+
// preprocessor. This adds double quotes around it and escapes any
35+
// double quotes and backslashes in the string.
36+
func QuoteCppString(str string) string {
37+
str = strings.Replace(str, "\\", "\\\\", -1)
38+
str = strings.Replace(str, "\"", "\\\"", -1)
39+
return "\"" + str + "\""
40+
}
41+
42+
// SaveSketchItemCpp saves a preprocessed .cpp sketch file on disk
43+
func SaveSketchItemCpp(item *sketch.Item, buildPath string) error {
44+
45+
sketchName := filepath.Base(item.Path)
46+
47+
if err := os.MkdirAll(buildPath, os.FileMode(0755)); err != nil {
48+
return errors.Wrap(err, "unable to create a folder to save the sketch")
49+
}
50+
51+
destFile := filepath.Join(buildPath, sketchName+".cpp")
52+
53+
if err := ioutil.WriteFile(destFile, item.Source, os.FileMode(0644)); err != nil {
54+
return errors.Wrap(err, "unable to save the sketch on disk")
55+
}
56+
57+
return nil
58+
}
59+
60+
// LoadSketch collects all the files composing a sketch.
61+
// The parameter `sketchPath` holds a path pointing to a single sketch file or a sketch folder,
62+
// the path must be absolute.
63+
func LoadSketch(sketchPath, buildPath string) (*sketch.Sketch, error) {
64+
stat, err := os.Stat(sketchPath)
65+
if err != nil {
66+
return nil, errors.Wrap(err, "unable to stat Sketch location")
67+
}
68+
69+
var sketchFolder, mainSketchFile string
70+
71+
// if a sketch folder was passed, save the parent and point sketchPath to the main .ino file
72+
if stat.IsDir() {
73+
sketchFolder = sketchPath
74+
mainSketchFile = filepath.Join(sketchPath, stat.Name()+".ino")
75+
// in the case a dir was passed, ensure the main file exists and is readable
76+
f, err := os.Open(mainSketchFile)
77+
if err != nil {
78+
return nil, errors.Wrap(err, "unable to find the main sketch file")
79+
}
80+
f.Close()
81+
} else {
82+
sketchFolder = filepath.Dir(sketchPath)
83+
mainSketchFile = sketchPath
84+
}
85+
86+
// collect all the sketch files
87+
var files []string
88+
err = filepath.Walk(sketchFolder, func(path string, info os.FileInfo, err error) error {
89+
// ignore hidden files and skip hidden directories
90+
if strings.HasPrefix(info.Name(), ".") {
91+
if info.IsDir() {
92+
return filepath.SkipDir
93+
}
94+
return nil
95+
}
96+
97+
// skip legacy SCM directories
98+
if info.IsDir() && strings.HasPrefix(info.Name(), "CVS") || strings.HasPrefix(info.Name(), "RCS") {
99+
return filepath.SkipDir
100+
}
101+
102+
// ignore directory entries
103+
if info.IsDir() {
104+
return nil
105+
}
106+
107+
// ignore if file extension doesn't match
108+
ext := strings.ToLower(filepath.Ext(path))
109+
_, isMain := globals.MainFileValidExtensions[ext]
110+
_, isAdditional := globals.AdditionalFileValidExtensions[ext]
111+
if !(isMain || isAdditional) {
112+
return nil
113+
}
114+
115+
// check if file is readable
116+
f, err := os.Open(path)
117+
if err != nil {
118+
return nil
119+
}
120+
f.Close()
121+
122+
// collect the file
123+
files = append(files, path)
124+
125+
// done
126+
return nil
127+
})
128+
129+
if err != nil {
130+
return nil, errors.Wrap(err, "there was an error while collecting the sketch files")
131+
}
132+
133+
return sketch.New(sketchFolder, mainSketchFile, buildPath, files)
134+
}
135+
136+
// MergeSketchSources merges all the source files included in a sketch
137+
func MergeSketchSources(sketch *sketch.Sketch) (int, string) {
138+
lineOffset := 0
139+
mergedSource := ""
140+
141+
// add Arduino.h inclusion directive if missing
142+
if !includesArduinoH.MatchString(sketch.MainFile.GetSourceStr()) {
143+
mergedSource += "#include <Arduino.h>\n"
144+
lineOffset++
145+
}
146+
147+
mergedSource += "#line 1 " + QuoteCppString(sketch.MainFile.Path) + "\n"
148+
mergedSource += sketch.MainFile.GetSourceStr() + "\n"
149+
lineOffset++
150+
151+
for _, item := range sketch.OtherSketchFiles {
152+
mergedSource += "#line 1 " + QuoteCppString(item.Path) + "\n"
153+
mergedSource += item.GetSourceStr() + "\n"
154+
}
155+
156+
return lineOffset, mergedSource
157+
}

Diff for: arduino/builder/sketch_test.go

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2019 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 modify or
12+
// otherwise use the software for commercial activities involving the Arduino
13+
// software without disclosing the source code of your own applications. To purchase
14+
// a commercial license, send an email to [email protected].
15+
16+
package builder_test
17+
18+
import (
19+
"io/ioutil"
20+
"os"
21+
"path/filepath"
22+
"strings"
23+
"testing"
24+
25+
"github.com/arduino/arduino-cli/arduino/builder"
26+
"github.com/arduino/arduino-cli/arduino/sketch"
27+
"github.com/stretchr/testify/assert"
28+
)
29+
30+
func TestSaveSketch(t *testing.T) {
31+
sketchName := t.Name() + ".ino"
32+
outName := sketchName + ".cpp"
33+
sketchFile := filepath.Join("testdata", sketchName)
34+
tmp := tmpDirOrDie()
35+
defer os.RemoveAll(tmp)
36+
source, err := ioutil.ReadFile(sketchFile)
37+
if err != nil {
38+
t.Fatalf("unable to read golden file %s: %v", sketchFile, err)
39+
}
40+
41+
builder.SaveSketchItemCpp(&sketch.Item{Path: sketchName, Source: source}, tmp)
42+
43+
out, err := ioutil.ReadFile(filepath.Join(tmp, outName))
44+
if err != nil {
45+
t.Fatalf("unable to read output file %s: %v", outName, err)
46+
}
47+
48+
assert.Equal(t, source, out)
49+
}
50+
51+
func TestLoadSketchFolder(t *testing.T) {
52+
// pass the path to the sketch folder
53+
sketchPath := filepath.Join("testdata", t.Name())
54+
mainFilePath := filepath.Join(sketchPath, t.Name()+".ino")
55+
s, err := builder.LoadSketch(sketchPath, "")
56+
assert.Nil(t, err)
57+
assert.NotNil(t, s)
58+
assert.Equal(t, mainFilePath, s.MainFile.Path)
59+
assert.Equal(t, sketchPath, s.LocationPath)
60+
assert.Len(t, s.OtherSketchFiles, 2)
61+
assert.Equal(t, "old.pde", filepath.Base(s.OtherSketchFiles[0].Path))
62+
assert.Equal(t, "other.ino", filepath.Base(s.OtherSketchFiles[1].Path))
63+
assert.Len(t, s.AdditionalFiles, 3)
64+
assert.Equal(t, "header.h", filepath.Base(s.AdditionalFiles[0].Path))
65+
assert.Equal(t, "s_file.S", filepath.Base(s.AdditionalFiles[1].Path))
66+
assert.Equal(t, "helper.h", filepath.Base(s.AdditionalFiles[2].Path))
67+
68+
// pass the path to the main file
69+
sketchPath = mainFilePath
70+
s, err = builder.LoadSketch(sketchPath, "")
71+
assert.Nil(t, err)
72+
assert.NotNil(t, s)
73+
assert.Equal(t, mainFilePath, s.MainFile.Path)
74+
assert.Len(t, s.OtherSketchFiles, 2)
75+
assert.Equal(t, "old.pde", filepath.Base(s.OtherSketchFiles[0].Path))
76+
assert.Equal(t, "other.ino", filepath.Base(s.OtherSketchFiles[1].Path))
77+
assert.Len(t, s.AdditionalFiles, 3)
78+
assert.Equal(t, "header.h", filepath.Base(s.AdditionalFiles[0].Path))
79+
assert.Equal(t, "s_file.S", filepath.Base(s.AdditionalFiles[1].Path))
80+
assert.Equal(t, "helper.h", filepath.Base(s.AdditionalFiles[2].Path))
81+
}
82+
83+
func TestLoadSketchFolderWrongMain(t *testing.T) {
84+
sketchPath := filepath.Join("testdata", t.Name())
85+
_, err := builder.LoadSketch(sketchPath, "")
86+
assert.Error(t, err)
87+
assert.Contains(t, err.Error(), "unable to find the main sketch file")
88+
89+
_, err = builder.LoadSketch("does/not/exist", "")
90+
assert.Error(t, err)
91+
assert.Contains(t, err.Error(), "no such file or directory")
92+
}
93+
94+
func TestMergeSketchSources(t *testing.T) {
95+
// borrow the sketch from TestLoadSketchFolder to avoid boilerplate
96+
s, err := builder.LoadSketch(filepath.Join("testdata", "TestLoadSketchFolder"), "")
97+
assert.Nil(t, err)
98+
assert.NotNil(t, s)
99+
100+
// load expected result
101+
mergedPath := filepath.Join("testdata", t.Name()+".txt")
102+
mergedBytes, err := ioutil.ReadFile(mergedPath)
103+
if err != nil {
104+
t.Fatalf("unable to read golden file %s: %v", mergedPath, err)
105+
}
106+
107+
offset, source := builder.MergeSketchSources(s)
108+
assert.Equal(t, 2, offset)
109+
assert.Equal(t, string(mergedBytes), source)
110+
}
111+
112+
func TestMergeSketchSourcesArduinoIncluded(t *testing.T) {
113+
s, err := builder.LoadSketch(filepath.Join("testdata", t.Name()), "")
114+
assert.Nil(t, err)
115+
assert.NotNil(t, s)
116+
117+
// ensure not to include Arduino.h when it's already there
118+
_, source := builder.MergeSketchSources(s)
119+
assert.Equal(t, 1, strings.Count(source, "<Arduino.h>"))
120+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
void setup()
2+
void loop) }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
void setup() {
2+
3+
}
4+
5+
void loop() {
6+
7+
}

Diff for: arduino/builder/testdata/TestLoadSketchFolder/doc.txt

Whitespace-only changes.
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#define FOO "BAR"

Diff for: arduino/builder/testdata/TestLoadSketchFolder/old.pde

Whitespace-only changes.
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
String hello() {
2+
return "world";
3+
}

Diff for: arduino/builder/testdata/TestLoadSketchFolder/s_file.S

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
#include <testlib4.h>
2+
#error "Whattya looking at?"

Diff for: arduino/builder/testdata/TestLoadSketchFolder/src/helper.h

Whitespace-only changes.

Diff for: arduino/builder/testdata/TestLoadSketchFolderWrongMain/main.ino

Whitespace-only changes.

Diff for: arduino/builder/testdata/TestMergeSketchSources.txt

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <Arduino.h>
2+
#line 1 "testdata/TestLoadSketchFolder/TestLoadSketchFolder.ino"
3+
void setup() {
4+
5+
}
6+
7+
void loop() {
8+
9+
}
10+
#line 1 "testdata/TestLoadSketchFolder/old.pde"
11+
12+
#line 1 "testdata/TestLoadSketchFolder/other.ino"
13+
String hello() {
14+
return "world";
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// a comment
2+
3+
# include <Arduino.h>
4+
5+
void setup() {
6+
}
7+
8+
void loop() {
9+
}

Diff for: arduino/builder/testdata/TestSaveSketch.ino

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#include <Bridge.h>
2+
#include <IRremote.h>
3+
#include <IRremoteInt.h>
4+
5+
void setup() {}
6+
void loop() {}

Diff for: arduino/globals/globals.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2019 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 modify or
12+
// otherwise use the software for commercial activities involving the Arduino
13+
// software without disclosing the source code of your own applications. To purchase
14+
// a commercial license, send an email to [email protected].
15+
16+
package globals
17+
18+
var (
19+
empty struct{}
20+
21+
// MainFileValidExtensions lists valid extensions for a sketch file
22+
MainFileValidExtensions = map[string]struct{}{
23+
".ino": empty,
24+
".pde": empty,
25+
}
26+
27+
// AdditionalFileValidExtensions lists any file extension the builder considers as valid
28+
AdditionalFileValidExtensions = map[string]struct{}{
29+
".h": empty,
30+
".c": empty,
31+
".hpp": empty,
32+
".hh": empty,
33+
".cpp": empty,
34+
".s": empty,
35+
}
36+
37+
// SourceFilesValidExtensions lists valid extensions for source files (no headers)
38+
SourceFilesValidExtensions = map[string]struct{}{
39+
".c": empty,
40+
".cpp": empty,
41+
".s": empty,
42+
}
43+
)

0 commit comments

Comments
 (0)