Skip to content

Commit a337fc4

Browse files
committed
Added 'source override' feature to compile command.
This feature allows to selectively "override" the content of a sketch. Overridden files will not be read from disk as usual but fetched directly from gRPC paramaters (if the cli is running as daemon) or from a .json file containing the new source code (if running from command line).
1 parent d0e904c commit a337fc4

File tree

8 files changed

+151
-83
lines changed

8 files changed

+151
-83
lines changed

arduino/builder/sketch.go

+36-18
Original file line numberDiff line numberDiff line change
@@ -216,12 +216,23 @@ func SketchLoad(sketchPath, buildPath string) (*sketch.Sketch, error) {
216216
}
217217

218218
// SketchMergeSources merges all the source files included in a sketch
219-
func SketchMergeSources(sketch *sketch.Sketch) (int, string, error) {
219+
func SketchMergeSources(sk *sketch.Sketch, overrides map[string]string) (int, string, error) {
220220
lineOffset := 0
221221
mergedSource := ""
222222

223+
getSource := func(i *sketch.Item) (string, error) {
224+
path, err := filepath.Rel(sk.LocationPath, i.Path)
225+
if err != nil {
226+
return "", errors.Wrap(err, "unable to compute relative path to the sketch for the item")
227+
}
228+
if override, ok := overrides[path]; ok {
229+
return override, nil
230+
}
231+
return i.GetSourceStr()
232+
}
233+
223234
// add Arduino.h inclusion directive if missing
224-
mainSrc, err := sketch.MainFile.GetSourceStr()
235+
mainSrc, err := getSource(sk.MainFile)
225236
if err != nil {
226237
return 0, "", err
227238
}
@@ -230,12 +241,12 @@ func SketchMergeSources(sketch *sketch.Sketch) (int, string, error) {
230241
lineOffset++
231242
}
232243

233-
mergedSource += "#line 1 " + QuoteCppString(sketch.MainFile.Path) + "\n"
244+
mergedSource += "#line 1 " + QuoteCppString(sk.MainFile.Path) + "\n"
234245
mergedSource += mainSrc + "\n"
235246
lineOffset++
236247

237-
for _, item := range sketch.OtherSketchFiles {
238-
src, err := item.GetSourceStr()
248+
for _, item := range sk.OtherSketchFiles {
249+
src, err := getSource(item)
239250
if err != nil {
240251
return 0, "", err
241252
}
@@ -248,7 +259,7 @@ func SketchMergeSources(sketch *sketch.Sketch) (int, string, error) {
248259

249260
// SketchCopyAdditionalFiles copies the additional files for a sketch to the
250261
// specified destination directory.
251-
func SketchCopyAdditionalFiles(sketch *sketch.Sketch, destPath string) error {
262+
func SketchCopyAdditionalFiles(sketch *sketch.Sketch, destPath string, overrides map[string]string) error {
252263
if err := os.MkdirAll(destPath, os.FileMode(0755)); err != nil {
253264
return errors.Wrap(err, "unable to create a folder to save the sketch files")
254265
}
@@ -265,7 +276,20 @@ func SketchCopyAdditionalFiles(sketch *sketch.Sketch, destPath string) error {
265276
return errors.Wrap(err, "unable to create the folder containing the item")
266277
}
267278

268-
err = writeIfDifferent(item.Path, targetPath)
279+
var sourceBytes []byte
280+
if override, ok := overrides[relpath]; ok {
281+
// use override source
282+
sourceBytes = []byte(override)
283+
} else {
284+
// read the source file
285+
s, err := item.GetSourceBytes()
286+
if err != nil {
287+
return errors.Wrap(err, "unable to read contents of the source item")
288+
}
289+
sourceBytes = s
290+
}
291+
292+
err = writeIfDifferent(sourceBytes, targetPath)
269293
if err != nil {
270294
return errors.Wrap(err, "unable to write to destination file")
271295
}
@@ -274,18 +298,12 @@ func SketchCopyAdditionalFiles(sketch *sketch.Sketch, destPath string) error {
274298
return nil
275299
}
276300

277-
func writeIfDifferent(sourcePath, destPath string) error {
278-
// read the source file
279-
newbytes, err := ioutil.ReadFile(sourcePath)
280-
if err != nil {
281-
return errors.Wrap(err, "unable to read contents of the source item")
282-
}
283-
301+
func writeIfDifferent(source []byte, destPath string) error {
284302
// check whether the destination file exists
285-
_, err = os.Stat(destPath)
303+
_, err := os.Stat(destPath)
286304
if os.IsNotExist(err) {
287305
// write directly
288-
return ioutil.WriteFile(destPath, newbytes, os.FileMode(0644))
306+
return ioutil.WriteFile(destPath, source, os.FileMode(0644))
289307
}
290308

291309
// read the destination file if it ex
@@ -295,8 +313,8 @@ func writeIfDifferent(sourcePath, destPath string) error {
295313
}
296314

297315
// overwrite if contents are different
298-
if bytes.Compare(existingBytes, newbytes) != 0 {
299-
return ioutil.WriteFile(destPath, newbytes, os.FileMode(0644))
316+
if bytes.Compare(existingBytes, source) != 0 {
317+
return ioutil.WriteFile(destPath, source, os.FileMode(0644))
300318
}
301319

302320
// source and destination are the same, don't write anything

arduino/builder/sketch_test.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func TestMergeSketchSources(t *testing.T) {
180180
t.Fatalf("unable to read golden file %s: %v", mergedPath, err)
181181
}
182182

183-
offset, source, err := builder.SketchMergeSources(s)
183+
offset, source, err := builder.SketchMergeSources(s, nil)
184184
require.Nil(t, err)
185185
require.Equal(t, 2, offset)
186186
require.Equal(t, string(mergedBytes), source)
@@ -192,7 +192,7 @@ func TestMergeSketchSourcesArduinoIncluded(t *testing.T) {
192192
require.NotNil(t, s)
193193

194194
// ensure not to include Arduino.h when it's already there
195-
_, source, err := builder.SketchMergeSources(s)
195+
_, source, err := builder.SketchMergeSources(s, nil)
196196
require.Nil(t, err)
197197
require.Equal(t, 1, strings.Count(source, "<Arduino.h>"))
198198
}
@@ -208,7 +208,7 @@ func TestCopyAdditionalFiles(t *testing.T) {
208208

209209
// copy the sketch over, create a fake main file we don't care about it
210210
// but we need it for `SketchLoad` to succeed later
211-
err = builder.SketchCopyAdditionalFiles(s1, tmp)
211+
err = builder.SketchCopyAdditionalFiles(s1, tmp, nil)
212212
require.Nil(t, err)
213213
fakeIno := filepath.Join(tmp, fmt.Sprintf("%s.ino", filepath.Base(tmp)))
214214
require.Nil(t, ioutil.WriteFile(fakeIno, []byte{}, os.FileMode(0644)))
@@ -223,7 +223,7 @@ func TestCopyAdditionalFiles(t *testing.T) {
223223
require.Nil(t, err)
224224

225225
// copy again
226-
err = builder.SketchCopyAdditionalFiles(s1, tmp)
226+
err = builder.SketchCopyAdditionalFiles(s1, tmp, nil)
227227
require.Nil(t, err)
228228

229229
// verify file hasn't changed

cli/compile/compile.go

+22
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package compile
1818
import (
1919
"bytes"
2020
"context"
21+
"encoding/json"
2122
"os"
2223

2324
"github.com/arduino/arduino-cli/cli/feedback"
@@ -54,6 +55,7 @@ var (
5455
programmer string // Use the specified programmer to upload
5556
clean bool // Cleanup the build folder and do not use any cached build
5657
exportBinaries bool // Copies compiled binaries to sketch folder when true
58+
sourceOverrides string // Path to a .json file that contains a set of replacements of the sketch source code.
5759
)
5860

5961
// NewCommand created a new `compile` command
@@ -100,6 +102,8 @@ func NewCommand() *cobra.Command {
100102
// This must be done because the value is set when the binding is accessed from viper. Accessing from cobra would only
101103
// read the value if the flag is set explicitly by the user.
102104
command.Flags().BoolP("export-binaries", "e", false, "If set built binaries will be exported to the sketch folder.")
105+
command.Flags().StringVar(&sourceOverrides, "source-override", "", "Optional. Path to a .json file that contains a set of replacements of the sketch source code.")
106+
command.Flag("source-override").Hidden = true
103107

104108
configuration.Settings.BindPFlag("sketch.always_export_binaries", command.Flags().Lookup("export-binaries"))
105109

@@ -126,6 +130,23 @@ func run(cmd *cobra.Command, args []string) {
126130
// the config file and the env vars.
127131
exportBinaries = configuration.Settings.GetBool("sketch.always_export_binaries")
128132

133+
var overrides map[string]string
134+
if sourceOverrides != "" {
135+
data, err := paths.New(sourceOverrides).ReadFile()
136+
if err != nil {
137+
feedback.Errorf("Error opening source code overrides data file: %v", err)
138+
os.Exit(errorcodes.ErrGeneric)
139+
}
140+
var o struct {
141+
Overrides map[string]string `json:"overrides"`
142+
}
143+
if err := json.Unmarshal(data, &o); err != nil {
144+
feedback.Errorf("Error: invalid source code overrides data file: %v", err)
145+
os.Exit(errorcodes.ErrGeneric)
146+
}
147+
overrides = o.Overrides
148+
}
149+
129150
compileReq := &rpc.CompileReq{
130151
Instance: inst,
131152
Fqbn: fqbn,
@@ -144,6 +165,7 @@ func run(cmd *cobra.Command, args []string) {
144165
OptimizeForDebug: optimizeForDebug,
145166
Clean: clean,
146167
ExportBinaries: exportBinaries,
168+
SourceOverride: overrides,
147169
}
148170
compileOut := new(bytes.Buffer)
149171
compileErr := new(bytes.Buffer)

commands/compile/compile.go

+2
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,8 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W
189189
builderCtx.SetLogger(i18n.LoggerToCustomStreams{Stdout: outStream, Stderr: errStream})
190190
builderCtx.Clean = req.GetClean()
191191

192+
builderCtx.SourceOverride = req.GetSourceOverride()
193+
192194
// Use defer() to create an rpc.CompileResp with all the information available at the
193195
// moment of return.
194196
defer func() {

legacy/builder/container_merge_copy_sketch_files.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func (s *ContainerMergeCopySketchFiles) Run(ctx *types.Context) error {
2828
if sk == nil {
2929
return errors.New("unable to convert legacy sketch to the new type")
3030
}
31-
offset, source, err := bldr.SketchMergeSources(sk)
31+
offset, source, err := bldr.SketchMergeSources(sk, ctx.SourceOverride)
3232
if err != nil {
3333
return err
3434
}
@@ -39,7 +39,7 @@ func (s *ContainerMergeCopySketchFiles) Run(ctx *types.Context) error {
3939
return errors.WithStack(err)
4040
}
4141

42-
if err := bldr.SketchCopyAdditionalFiles(sk, ctx.SketchBuildPath.String()); err != nil {
42+
if err := bldr.SketchCopyAdditionalFiles(sk, ctx.SketchBuildPath.String(), ctx.SourceOverride); err != nil {
4343
return errors.WithStack(err)
4444
}
4545

legacy/builder/types/context.go

+5
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,11 @@ type Context struct {
163163

164164
// Sizer results
165165
ExecutableSectionsSize ExecutablesFileSections
166+
167+
// Source code overrides (filename -> content map).
168+
// The provided source data is used instead of reading it from disk.
169+
// The keys of the map are paths relative to sketch folder.
170+
SourceOverride map[string]string
166171
}
167172

168173
// ExecutableSectionSize represents a section of the executable output file

0 commit comments

Comments
 (0)