Skip to content

Commit 15ceb91

Browse files
authored
add README.md generation in the sketch-dist folder (#17)
* add README.md generation in the `sketch-dist` folder The generated README.md contains commands that helps on how to replicate the build environment * apply suggestions from code review and move file creation in functions for better readability * fix typo
1 parent 031ad53 commit 15ceb91

File tree

2 files changed

+99
-20
lines changed

2 files changed

+99
-20
lines changed

README.md

+21-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
# arduino-cslt
22

33
`arduino-cslt` is a convenient wrapper of [arduino-cli](https://github.com/arduino/arduino-cli), it compiles Arduino sketches outputting a precompiled library under `sketch-dist/` folder created in the current working directory.
4-
It generates a json file in the `extras/` folder that contains information regarding libraries and core to use in order to build the sketch. The result is achieved by parsing the verbose output of `arduino-cli` and by using [GNU ar](https://sourceware.org/binutils/docs/binutils/ar.html) to generate an archive of the object files.
4+
It generates a README.md file that contains information regarding libraries and core to use in order to build the sketch. The result is achieved by parsing the verbose output of `arduino-cli` and by using [GNU ar](https://sourceware.org/binutils/docs/binutils/ar.html) to generate an archive of the object files.
55

6-
## Prequisites
6+
## Prerequisites
77
In order to run this tool you have to install first the [Arduino CLI](https://github.com/arduino/arduino-cli) and have `arduino-cli` binary in your `$PATH`, otherwise `arduino-cslt` won't work.
88
Please use a version of the Arduino CLI that has [this](https://github.com/arduino/arduino-cli/pull/1608) change (version > 0.20.2).
99

@@ -15,7 +15,7 @@ In order to build `arduino-cslt` just use `task go:build`
1515
## Usage
1616
`./arduino-cslt compile -b <fqbn> <sketch_path>`
1717

18-
[![asciicast](https://asciinema.org/a/463342.svg)](https://asciinema.org/a/463342)
18+
[![asciicast](https://asciinema.org/a/465059.svg)](https://asciinema.org/a/465059)
1919

2020
For example, running `./arduino-cslt compile -b arduino:samd:mkrwifi1010 sketch/sketch.ino` should produce a library with the following structure, in the current working directory:
2121
```
@@ -28,6 +28,7 @@ sketch-dist/
2828
│ ├── cortex-m0plus
2929
│ │ └── libsketch.a
3030
│ └── libsketch.h
31+
├── README.md <--contains information regarding libraries and core to install in order to reproduce the original build environment
3132
└── sketch
3233
└── sketch.ino <-- the actual sketch we are going to compile with the arduino-cli later
3334
```
@@ -48,11 +49,25 @@ INFO[0001] restored sketch/sketch.ino
4849
INFO[0001] created sketch-dist/libsketch/library.properties
4950
INFO[0001] created sketch-dist/libsketch/src/libsketch.h
5051
INFO[0001] created sketch-dist/sketch/sketch.ino
52+
INFO[0003] created sketch-dist/README.md
5153
INFO[0001] running: gcc-ar rcs sketch-dist/libsketch/src/cortex-m0plus/libsketch.a /tmp/arduino-sketch-E4D76B1781E9EB73A7B3491CAC68F374/sketch/sketch.ino.cpp.o
5254
INFO[0001] created sketch-dist/libsketch/src/cortex-m0plus/libsketch.a
5355
INFO[0001] created sketch-dist/libsketch/extras/result.json
5456
```
5557

58+
The content of `sketch-dist/README.md` included copy-pastable commands to reproduce the build environment:
59+
```markdown
60+
This package contains firmware code loaded in your product.
61+
The firmware contains additional code licensed with LGPL clause; in order to re-compile the entire firmware bundle, please execute the following.
62+
63+
## Install core and libraries
64+
`arduino-cli core install arduino:[email protected]`
65+
`arduino-cli lib install [email protected] [email protected]`
66+
67+
## Compile
68+
`arduino-cli compile -b arduino:samd:mkrwifi1010 sketch-dist/sketch/sketch.ino --library sketch-dist/libsketch`
69+
```
70+
5671
And the content of `sketch-dist/libsketch/extras/result.json` is:
5772
```json
5873
{
@@ -80,12 +95,12 @@ And the content of `sketch-dist/libsketch/extras/result.json` is:
8095
```
8196

8297
## How to compile the precompiled sketch
83-
In order to compile the sketch you have first to install manually the libraries and the core listed in the `sketch-dist/<libsketch>/extras/result.json` file.
84-
85-
You can install a library with [`arduino-cli lib install LIBRARY[@VERSION_NUMBER]`](https://arduino.github.io/arduino-cli/latest/commands/arduino-cli_lib_install/).
98+
In order to compile the sketch you can follow the instructions listed in the `sketch-dist/README.md` file.
8699

87100
You can install a core with [`arduino-cli core install PACKAGER:ARCH[@VERSION]`](https://arduino.github.io/arduino-cli/latest/commands/arduino-cli_core_install/).
88101

102+
You can install a library with [`arduino-cli lib install LIBRARY[@VERSION_NUMBER]`](https://arduino.github.io/arduino-cli/latest/commands/arduino-cli_lib_install/).
103+
89104
After completing that operation you can compile it with:
90105

91106
`arduino-cli compile -b <fqbn> sketch-dist/sketch/sketch.ino --library sketch-dist/<libsketch>`.

cmd/compile.go

+78-14
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package cmd
77
import (
88
"bytes"
99
"encoding/json"
10+
"fmt"
1011
"os"
1112
"os/exec"
1213
"strings"
@@ -64,10 +65,9 @@ var compileCmd = &cobra.Command{
6465
│ ├── cortex-m0plus
6566
│ │ └── libsketch.a
6667
│ └── libsketch.h
68+
├── README.md <--contains information regarding libraries and core to install in order to reproduce the original build environment
6769
└── sketch
68-
└── sketch.ino <-- the actual sketch we are going to compile with the arduino-cli later
69-
70-
The result.json file contains information regarding libraries and core to use in order to reproduce the original build environment`,
70+
└── sketch.ino <-- the actual sketch we can recompile with the arduino-cli later`,
7171
Example: os.Args[0] + `compile -b arduino:samd:mkrwifi1010 sketch/sketch.ino`,
7272
Args: cobra.ExactArgs(1), // the path of the sketch to build
7373
Run: compileSketch,
@@ -141,7 +141,7 @@ func compileSketch(cmd *cobra.Command, args []string) {
141141

142142
sketchName := strings.TrimSuffix(inoPath.Base(), inoPath.Ext())
143143
// let's create the library corresponding to the precompiled sketch
144-
createLib(sketchName, buildMcu, returnJson, objFilePaths)
144+
createLib(sketchName, buildMcu, fqbn, returnJson, objFilePaths)
145145
}
146146

147147
// parseCliCompileOutput function takes cmdOutToParse as argument,
@@ -218,7 +218,7 @@ func getInoSketchPath(argSketchPath string) (inoPath *paths.Path) {
218218
return inoPath
219219
}
220220

221-
// createMainCpp function, as the name suggests. will create a main.cpp file inside inoPath
221+
// createMainCpp function will create a main.cpp file inside inoPath
222222
// we do this because setup() and loop() functions will be replaced inside the ino file, in order to allow the linking afterwards
223223
// creating this file is mandatory, we include also Arduino.h because it's a step done by the builder during the building phase, but only for ino files
224224
func createMainCpp(inoPath *paths.Path) {
@@ -238,7 +238,7 @@ _loop();
238238
createFile(mainCppPath, mainCpp)
239239
}
240240

241-
// removeMainCpp function, as the name suggests. will remove a main.cpp file inside inoPath
241+
// removeMainCpp function will remove a main.cpp file inside inoPath
242242
// we do this after the compile has been completed, this way we can rerun arduino-cslt again.
243243
// If we do not remove this file and run the compile again it will fail because a main.cpp file with the same definitions is already present
244244
func removeMainCpp(inoPath *paths.Path) {
@@ -274,9 +274,10 @@ func patchSketch(inoPath *paths.Path) (oldSketchContent []byte) {
274274
// createLib function will take care of creating the library directory structure and files required, for the precompiled library to be recognized as such.
275275
// sketchName is the name of the sketch without the .ino extension. We use this for the name of the lib.
276276
// buildMcu is the name of the MCU of the board we have compiled for. The library specifications (https://arduino.github.io/arduino-cli/0.20/library-specification/#precompiled-binaries) requires that the precompiled archive is stored inside a folder with the name of the MCU used during the compile.
277+
// fqbn is required in order to generate the README.md file with instructions.
277278
// returnJson is the ResultJson object containing informations regarding core and libraries used during the compile process.
278279
// objFilePaths is a paths.PathList containing the paths.Paths to all the sketch related object files produced during the compile phase.
279-
func createLib(sketchName string, buildMcu string, returnJson *ResultJson, objFilePaths *paths.PathList) {
280+
func createLib(sketchName, buildMcu, fqbn string, returnJson *ResultJson, objFilePaths *paths.PathList) {
280281
// we are going to leverage the precompiled library infrastructure to make the linking work.
281282
// this type of lib, as the type suggest, is already compiled so it only gets linked during the linking phase of a sketch
282283
// but we have to create a library folder structure in the current directory:
@@ -290,6 +291,7 @@ func createLib(sketchName string, buildMcu string, returnJson *ResultJson, objFi
290291
// │ ├── cortex-m0plus
291292
// │ │ └── libsketch.a
292293
// │ └── libsketch.h
294+
// ├── README.md <--contains information regarding libraries and core to install in order to reproduce the original build environment
293295
// └── sketch
294296
// └── sketch.ino <-- the actual sketch we are going to compile with the arduino-cli later
295297

@@ -327,7 +329,22 @@ func createLib(sketchName string, buildMcu string, returnJson *ResultJson, objFi
327329

328330
// let's create the files
329331

330-
// create a library.properties file in the root dir of the lib
332+
createLibraryPropertiesFile(sketchName, libDir)
333+
334+
createLibSketchHeaderFile(sketchName, srcDir, returnJson)
335+
336+
sketchFilePath := createSketchFile(sketchName, sketchDir)
337+
338+
createReadmeMdFile(sketchFilePath, libDir, workingDir, rootDir, returnJson)
339+
340+
createArchiveFile(sketchName, objFilePaths, srcDir)
341+
342+
createResultJsonFile(extraDir, returnJson)
343+
}
344+
345+
// createLibraryPropertiesFile will create a library.properties file in the libDir,
346+
// the sketchName is required in order to correctly set the name of the "library"
347+
func createLibraryPropertiesFile(sketchName string, libDir *paths.Path) {
331348
// the library.properties contains the following:
332349
libraryProperties := `name=` + sketchName + `
333350
author=TODO
@@ -341,6 +358,15 @@ precompiled=true`
341358
libraryPropertyPath := libDir.Join("library.properties")
342359
createFile(libraryPropertyPath, libraryProperties)
343360

361+
}
362+
363+
// createLibSketchHeaderFile will create the libsketch header file,
364+
// the file will be created in the srcDir
365+
// This file has predeclarations of _setup() and _loop() functions declared originally in the main.cpp file (which is not included in the .a archive),
366+
// It is the counterpart of libsketch.a
367+
// we pass resultJson because from there we can extract infos regarding used libs
368+
// sketchName is used to name the file
369+
func createLibSketchHeaderFile(sketchName string, srcDir *paths.Path, returnJson *ResultJson) {
344370
// we calculate the #include part to append at the beginning of the header file here with all the libraries used by the original sketch
345371
var librariesIncludes []string
346372
for _, lib := range returnJson.LibsInfo {
@@ -349,18 +375,19 @@ precompiled=true`
349375
}
350376
}
351377

352-
// create the header file in the src/ dir
353-
// This file has predeclarations of _setup() and _loop() functions declared originally in the main.cpp file (which is not included in the .a archive),
354-
// It is the counterpart of libsketch.a
355378
// the libsketch.h contains the following:
356379
libsketchHeader := strings.Join(librariesIncludes, "\n") + `
357380
void _setup();
358381
void _loop();`
359382

360383
libsketchFilePath := srcDir.Parent().Join("lib" + sketchName + ".h")
361384
createFile(libsketchFilePath, libsketchHeader)
385+
}
362386

363-
// create the sketch file in the sketch-dist dir
387+
// createSketchFile will create the sketch which will be the entrypoint of the compilation with the arduino-cli
388+
// the sketch file will be created in the sketchDir
389+
// the sketchName argument is used to correctly include the right .h file
390+
func createSketchFile(sketchName string, sketchDir *paths.Path) *paths.Path {
364391
// This one will include the libsketch.h and basically is the replacement of main.cpp
365392
// the sketch.ino contains the following:
366393
sketchFile := `#include <` + "lib" + sketchName + `.h>
@@ -372,8 +399,41 @@ void loop() {
372399
}`
373400
sketchFilePath := sketchDir.Join(sketchName + ".ino")
374401
createFile(sketchFilePath, sketchFile)
402+
return sketchFilePath
403+
}
404+
405+
// createReadmeMdFile is a helper function that is reposnible for the generation of the README.md file containing informations on how to reproduce the build environment
406+
// it takes the resultJson and some paths.Paths as input to do the required calculations.. The name of the arguments should be sufficient to understand
407+
func createReadmeMdFile(sketchFilePath, libDir, workingDir, rootDir *paths.Path, returnJson *ResultJson) {
408+
// generate the commands to run to successfully reproduce the build environment, they will be used as content for the README.md
409+
var readmeContent []string
410+
readmeContent = append(readmeContent, "`arduino-cli core install "+returnJson.CoreInfo.Id+"@"+returnJson.CoreInfo.Version+"`")
411+
libs := []string{}
412+
for _, l := range returnJson.LibsInfo {
413+
libs = append(libs, l.Name+"@"+l.Version)
414+
}
415+
readmeContent = append(readmeContent, fmt.Sprintf("`arduino-cli lib install %s`", strings.Join(libs, " ")))
416+
// make the paths relative, absolute paths are too long and are different on the user machine
417+
sketchFileRelPath, _ := sketchFilePath.RelFrom(workingDir)
418+
libRelDir, _ := libDir.RelFrom(workingDir)
419+
readmeCompile := "`arduino-cli compile -b " + fqbn + " " + sketchFileRelPath.String() + " --library " + libRelDir.String() + "`"
420+
421+
//create the README.md file containig instructions regarding what commands to run in order to have again a working binary
422+
// the README.md contains the following:
423+
readmeMd := `This package contains firmware code loaded in your product.
424+
The firmware contains additional code licensed with LGPL clause; in order to re-compile the entire firmware bundle, please execute the following.
425+
426+
## Install core and libraries
427+
` + strings.Join(readmeContent, "\n") + "\n" + `
428+
## Compile
429+
` + readmeCompile + "\n"
430+
431+
readmeMdPath := rootDir.Join("README.md")
432+
createFile(readmeMdPath, readmeMd)
433+
}
375434

376-
// run gcc-ar to create an archive containing all the object files except the main.cpp.o (we don't need it because we have created a substitute of it before ⬆️)
435+
// createArchiveFile function will run `gcc-ar` to create an archive containing all the object files except the main.cpp.o (we don't need it because we have created a substitute of it before: sketchfile.ino)
436+
func createArchiveFile(sketchName string, objFilePaths *paths.PathList, srcDir *paths.Path) {
377437
// we exclude the main.cpp.o because we are going to link the archive libsketch.a against sketchName.ino
378438
objFilePaths.FilterOutPrefix("main.cpp")
379439
archivePath := srcDir.Join("lib" + sketchName + ".a")
@@ -388,6 +448,10 @@ void loop() {
388448
} else {
389449
logrus.Infof("created %s", archivePath.String())
390450
}
451+
}
452+
453+
// createResultJsonFile will generate the result.json file and save it in extraDir
454+
func createResultJsonFile(extraDir *paths.Path, returnJson *ResultJson) {
391455
// save the result.json in the library extra dir
392456
jsonFilePath := extraDir.Join("result.json")
393457
if jsonContents, err := json.MarshalIndent(returnJson, "", " "); err != nil {
@@ -402,7 +466,7 @@ void loop() {
402466
// createFile is an helper function useful to create a file,
403467
// it takes filePath and fileContent as arguments,
404468
// filePath points to the location where to save the file
405-
// fileContent,as the name suggests, include the content of the file
469+
// fileContent include the content of the file
406470
func createFile(filePath *paths.Path, fileContent string) {
407471
err := os.WriteFile(filePath.String(), []byte(fileContent), 0644)
408472
if err != nil {

0 commit comments

Comments
 (0)