The Wayback Machine - https://web.archive.org/web/20211231043223/https://github.com/arduino/arduino-cli/commit/ec027a7f435d63d34f0dfd21a8274ece655712e9
Skip to content
Permalink
Browse files
Add support for Pluggable Discoveries (#1333)
* [skip changelog] Add DiscoveryManager to PackageManager

* Add loading of PluggableDiscoveries when loading a platform release

* Added compatibility layer for non-pluggable platforms

* Implemented board list with discoveries

* Implemented discovery loading after initialization

* Implemented board watch with discoveries

* Fix load discoveries tests

* Fix some issues with board list watcher

* Fix FindToolsRequiredFromPlatformRelease not returning discoveries

* Enhanced handling of some discoveries states

* Fix PackageManager reset

* Add function to convert discovery.Port to rpc.Port

* Moved reference argument parsing to new package

* Fix functions docstrings

* Remove duplicated code to initialize Sketch path

* Add property conversion for platform not supporting pluggable discovery

* Fix board list watch not working

* Fix crash when converting Port to rpc struct

* Add generic Port argument

* Change gRPC upload functions to use new Port message

* Add support for upload user fields

* Fix upload unit tests

* Fix code naming issues

* Added builtin:mdns-discovery

* Do not panic if discovery tool is not installed

* Implemented port/protocol detection at CLI startup time

* Perform 1200bps-touch only on serial ports

* Added missing properties for pluggable upload

* Correctly implemented 'board list' timeout option

* Updated mdns-discovery to 0.9.2

* Add documentation

* Add board properties to board list command and gRPC function

* Fix documentation and code comments

Co-authored-by: per1234 <[email protected]>

* Fix crash when attempting upload without specifying port address

* Fix unit tests

* Update go-properties-orderedmap to fix discovery properties issues

* Fix more documentation

Co-authored-by: per1234 <[email protected]>

* Clarify pluggable discovery specification

* More documentation fixes

* Add upload_port properties docs in platform specification

* Change links from pluggable discovery RFC to official docs

* Add more upload mock integration tests

* Fix integration tests

* Change property to declare pluggable discoveries

* Change property to declare pluggable discoveries

* Fix documentation

Co-authored-by: per1234 <[email protected]>

* Fix loading of platform not supporting pluggable discovery

* Fix more documentation

Co-authored-by: per1234 <[email protected]>

* Add pluggable discovery states documentation

* Enhanced handling of pluggable discoveries states

* Discoveries processes are now killed if the HELLO command fails

* Add pluggable discovery logging

* Enhanced handling of failing pluggable discoveries

* Fix pluggable discoveries parallelization

* Discoveries event channels are now created when start sync is called

* Cached ports are now reset on discovery stop

* Renamed ListSync methods to ListCachedPorts

* Pluggable discovery upload user fields are now limited to 50 chars

* Fix i18n strings

* Fix failing integration tests

* Fix i18n data

* Fix integration tests again

* [skip changelog] Internationalize strings added for pluggable discovery support (#1384)

* Update docs/pluggable-discovery-specification.md

Co-authored-by: per1234 <[email protected]>

* Fix failing workflows

* Updated upload-mock tests for generation

* Added a lot of mock upload test (also with programmer option)

* test_upload_mock: Handle '{' and '}' in recipes

* network ota: autoconvert network_patter from legacy

* Automatically add port detection properties for network discovery

* Slightly improved 'board list' text output

* Default 'board list' timeout to 1s

* Added some code review fixes

* Added unit test for legacy-package conversion to pluggable discovery

Co-authored-by: Cristian Maglie <[email protected]>
Co-authored-by: per1234 <[email protected]>
  • Loading branch information
3 people committed Aug 23, 2021
1 parent 3aceff5 commit ec027a7f435d63d34f0dfd21a8274ece655712e9
Showing with 13,916 additions and 6,213 deletions.
  1. +2 −0 .flake8
  2. +191 −9 arduino/cores/packagemanager/loader.go
  3. +143 −0 arduino/cores/packagemanager/loader_test.go
  4. +56 −0 arduino/cores/packagemanager/package_manager.go
  5. +115 −2 arduino/cores/packagemanager/package_manager_test.go
  6. +0 −4,821 arduino/cores/packagemanager/testdata/data_dir_1/packages/esp8266/hardware/esp8266/2.4.2/boards.txt
  7. +0 −21 ...ino/cores/packagemanager/testdata/data_dir_1/packages/esp8266/hardware/esp8266/2.4.2/keywords.txt
  8. +0 −134 ...ino/cores/packagemanager/testdata/data_dir_1/packages/esp8266/hardware/esp8266/2.4.2/platform.txt
  9. +8,877 −0 arduino/cores/packagemanager/testdata/data_dir_1/packages/esp8266/hardware/esp8266/3.0.2/boards.txt
  10. +170 −0 ...ino/cores/packagemanager/testdata/data_dir_1/packages/esp8266/hardware/esp8266/3.0.2/platform.txt
  11. 0 ...kagemanager/testdata/data_dir_1/packages/esp8266/hardware/esp8266/{2.4.2 → 3.0.2}/programmers.txt
  12. +126 −57 arduino/discovery/discovery.go
  13. +13 −2 arduino/discovery/discovery_client/go.sum
  14. +5 −4 arduino/discovery/discovery_client/main.go
  15. +2 −2 arduino/discovery/discovery_test.go
  16. +259 −0 arduino/discovery/discoverymanager/discoverymanager.go
  17. +20 −0 cli/arguments/arguments.go
  18. +125 −0 cli/arguments/port.go
  19. +12 −12 cli/{globals/args.go → arguments/reference.go}
  20. +10 −10 cli/{globals/args_test.go → arguments/reference_test.go}
  21. +41 −0 cli/arguments/sketch.go
  22. +51 −0 cli/arguments/user_fields.go
  23. +5 −22 cli/board/attach.go
  24. +20 −25 cli/board/list.go
  25. +11 −3 cli/burnbootloader/burnbootloader.go
  26. +56 −42 cli/compile/compile.go
  27. +2 −2 cli/core/download.go
  28. +2 −2 cli/core/install.go
  29. +2 −2 cli/core/uninstall.go
  30. +2 −2 cli/core/upgrade.go
  31. +4 −20 cli/debug/debug.go
  32. +35 −23 cli/upload/upload.go
  33. +1 −1 client_example/go.sum
  34. +10 −7 client_example/main.go
  35. +60 −38 commands/board/list.go
  36. +0 −2 commands/board/list_test.go
  37. +109 −0 commands/bundled_tools_mdns_discovery.go
  38. +0 −68 commands/bundled_tools_serial_discovery.go
  39. +1 −1 commands/daemon/daemon.go
  40. +1 −1 commands/daemon/term_example/go.sum
  41. +20 −1 commands/instances.go
  42. +1 −0 commands/upload/burnbootloader.go
  43. +133 −44 commands/upload/upload.go
  44. +119 −18 commands/upload/upload_test.go
  45. +60 −0 docs/UPGRADING.md
  46. +20 −0 docs/img/pluggable-discovery-state-machine.dot
  47. BIN docs/img/pluggable-discovery-state-machine.png
  48. +9 −0 docs/package_index_json-specification.md
  49. +280 −12 docs/platform-specification.md
  50. +379 −0 docs/pluggable-discovery-specification.md
  51. +2 −2 docsgen/go.sum
  52. +1 −1 go.mod
  53. +2 −2 go.sum
  54. +385 −299 i18n/data/en.po
  55. +7 −7 i18n/rice-box.go
  56. +2 −0 mkdocs.yml
  57. +265 −300 rpc/cc/arduino/cli/commands/v1/board.pb.go
  58. +7 −14 rpc/cc/arduino/cli/commands/v1/board.proto
  59. +1 −1 rpc/cc/arduino/cli/commands/v1/commands.pb.go
  60. +24 −28 rpc/cc/arduino/cli/commands/v1/commands_grpc.pb.go
  61. +1 −1 rpc/cc/arduino/cli/commands/v1/common.pb.go
  62. +1 −1 rpc/cc/arduino/cli/commands/v1/compile.pb.go
  63. +1 −1 rpc/cc/arduino/cli/commands/v1/compile.proto
  64. +1 −1 rpc/cc/arduino/cli/commands/v1/core.pb.go
  65. +1 −1 rpc/cc/arduino/cli/commands/v1/lib.pb.go
  66. +1 −1 rpc/cc/arduino/cli/commands/v1/port.pb.go
  67. +444 −100 rpc/cc/arduino/cli/commands/v1/upload.pb.go
  68. +49 −3 rpc/cc/arduino/cli/commands/v1/upload.proto
  69. +1 −1 rpc/cc/arduino/cli/debug/v1/debug.pb.go
  70. +4 −8 rpc/cc/arduino/cli/debug/v1/debug_grpc.pb.go
  71. +1 −1 rpc/cc/arduino/cli/monitor/v1/monitor.pb.go
  72. +4 −8 rpc/cc/arduino/cli/monitor/v1/monitor_grpc.pb.go
  73. +1 −1 rpc/cc/arduino/cli/settings/v1/settings.pb.go
  74. +3 −7 rpc/cc/arduino/cli/settings/v1/settings_grpc.pb.go
  75. +1,147 −14 test/test_upload_mock.py
@@ -5,6 +5,8 @@

[flake8]
doctests = True
per-file-ignores =
test/test_upload_mock.py:E501
ignore =
E741,
# W503 and W504 are mutually exclusive. PEP 8 recommends line break before.
@@ -23,6 +23,7 @@ import (
"strings"

"github.com/arduino/arduino-cli/arduino/cores"
"github.com/arduino/arduino-cli/arduino/discovery"
"github.com/arduino/arduino-cli/configuration"
"github.com/arduino/go-paths-helper"
properties "github.com/arduino/go-properties-orderedmap"
@@ -320,8 +321,11 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
return fmt.Errorf(tr("loading %[1]s: %[2]s"), platformTxtLocalPath, err)
}

if platform.Properties.SubTree("discovery").Size() > 0 {
if platform.Properties.SubTree("pluggable_discovery").Size() > 0 {
platform.PluggableDiscoveryAware = true
} else {
platform.Properties.Set("pluggable_discovery.required.0", "builtin:serial-discovery")
platform.Properties.Set("pluggable_discovery.required.1", "builtin:mdns-discovery")
}

if platform.Platform.Name == "" {
@@ -337,8 +341,11 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p

// Create programmers properties
if programmersProperties, err := properties.SafeLoad(programmersTxtPath.String()); err == nil {
for programmerID, programmerProperties := range programmersProperties.FirstLevelOf() {
platform.Programmers[programmerID] = pm.loadProgrammer(programmerProperties)
for programmerID, programmerProps := range programmersProperties.FirstLevelOf() {
if !platform.PluggableDiscoveryAware {
convertUploadToolsToPluggableDiscovery(programmerProps)
}
platform.Programmers[programmerID] = pm.loadProgrammer(programmerProps)
platform.Programmers[programmerID].PlatformRelease = platform
}
} else {
@@ -349,9 +356,71 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
return fmt.Errorf(tr("loading boards: %s"), err)
}

if !platform.PluggableDiscoveryAware {
convertLegacyPlatformToPluggableDiscovery(platform)
}
return nil
}

func convertLegacyPlatformToPluggableDiscovery(platform *cores.PlatformRelease) {
toolsProps := platform.Properties.SubTree("tools").FirstLevelOf()
for toolName, toolProps := range toolsProps {
if !toolProps.ContainsKey("upload.network_pattern") {
continue
}

// Convert network_pattern configuration to pluggable discovery
convertedToolName := toolName + "__pluggable_network"
convertedProps := convertLegacyNetworkPatternToPluggableDiscovery(toolProps, convertedToolName)

// Merge the converted properties in the root configuration
platform.Properties.Merge(convertedProps)

// Add the network upload to the boards using the old method
for _, board := range platform.Boards {
oldUploadTool := board.Properties.Get("upload.tool")
if oldUploadTool == toolName && !board.Properties.ContainsKey("upload.tool.network") {
board.Properties.Set("upload.tool.network", convertedToolName)

// Add identification properties for network protocol
i := 0
for {
if !board.Properties.ContainsKey(fmt.Sprintf("upload_port.%d.vid", i)) {
break
}
i++
}
board.Properties.Set(fmt.Sprintf("upload_port.%d.board", i), board.BoardID)
}
}
}
}

func convertLegacyNetworkPatternToPluggableDiscovery(props *properties.Map, newToolName string) *properties.Map {
pattern, ok := props.GetOk("upload.network_pattern")
if !ok {
return nil
}
props.Remove("upload.network_pattern")
pattern = strings.ReplaceAll(pattern, "{serial.port}", "{upload.port.address}")
pattern = strings.ReplaceAll(pattern, "{network.port}", "{upload.port.properties.port}")
if strings.Contains(pattern, "{network.password}") {
props.Set("upload.field.password", "Password")
props.Set("upload.field.password.secret", "true")
pattern = strings.ReplaceAll(pattern, "{network.password}", "{upload.field.password}")
}
props.Set("upload.pattern", pattern)

prefix := "tools." + newToolName + "."
res := properties.NewMap()
for _, k := range props.Keys() {
v := props.Get(k)
res.Set(prefix+k, v)
// fmt.Println("ADDED:", prefix+k+"="+v)
}
return res
}

func (pm *PackageManager) loadProgrammer(programmerProperties *properties.Map) *cores.Programmer {
return &cores.Programmer{
Name: programmerProperties.Get("name"),
@@ -388,12 +457,6 @@ func (pm *PackageManager) loadBoards(platform *cores.PlatformRelease) error {
// set all other boards properties
delete(propertiesByBoard, "menu")

if !platform.PluggableDiscoveryAware {
for _, boardProperties := range propertiesByBoard {
convertVidPidIdentificationPropertiesToPluggableDiscovery(boardProperties)
}
}

skippedBoards := []string{}
for boardID, boardProperties := range propertiesByBoard {
var board *cores.Board
@@ -412,6 +475,12 @@ func (pm *PackageManager) loadBoards(platform *cores.PlatformRelease) error {
goto next_board
}
}

if !platform.PluggableDiscoveryAware {
convertVidPidIdentificationPropertiesToPluggableDiscovery(boardProperties)
convertUploadToolsToPluggableDiscovery(boardProperties)
}

// The board's ID must be available in a board's properties since it can
// be used in all configuration files for several reasons, like setting compilation
// flags depending on the board id.
@@ -468,6 +537,22 @@ func convertVidPidIdentificationPropertiesToPluggableDiscovery(boardProperties *
}
}

func convertUploadToolsToPluggableDiscovery(props *properties.Map) {
actions := []string{"upload", "bootloader", "program"}
for _, action := range actions {
if !props.ContainsKey(fmt.Sprintf("%s.tool.default", action)) {
tool, found := props.GetOk(fmt.Sprintf("%s.tool", action))
if !found {
// Just skip it, ideally this must never happen but if a platform
// doesn't define an expected upload.tool, bootloader.tool or program.tool
// there will be other issues further down the road after this conversion
continue
}
props.Set(fmt.Sprintf("%s.tool.default", action), tool)
}
}
}

func (pm *PackageManager) loadToolsFromPackage(targetPackage *cores.Package, toolsPath *paths.Path) []*status.Status {
pm.Log.Infof("Loading tools from dir: %s", toolsPath)

@@ -587,3 +672,100 @@ func (pm *PackageManager) LoadToolsFromBundleDirectory(toolsPath *paths.Path) er
}
return nil
}

// LoadDiscoveries load all discoveries for all loaded platforms
// Returns error if:
// * A PluggableDiscovery instance can't be created
// * Tools required by the PlatformRelease cannot be found
// * Command line to start PluggableDiscovery has malformed or mismatched quotes
func (pm *PackageManager) LoadDiscoveries() []*status.Status {
statuses := []*status.Status{}
for _, platform := range pm.InstalledPlatformReleases() {
statuses = append(statuses, pm.loadDiscoveries(platform)...)
}
return statuses
}

func (pm *PackageManager) loadDiscoveries(release *cores.PlatformRelease) []*status.Status {
statuses := []*status.Status{}
discoveryProperties := release.Properties.SubTree("pluggable_discovery")

if discoveryProperties.Size() == 0 {
return nil
}

// Handles discovery properties formatted like so:
//
// Case 1:
// "pluggable_discovery.required": "PLATFORM:DISCOVERY_NAME",
//
// Case 2:
// "pluggable_discovery.required.0": "PLATFORM:DISCOVERY_ID_1",
// "pluggable_discovery.required.1": "PLATFORM:DISCOVERY_ID_2",
//
// If both indexed and unindexed properties are found the unindexed are ignored
for _, id := range discoveryProperties.ExtractSubIndexLists("required") {
tool := pm.GetTool(id)
if tool == nil {
statuses = append(statuses, status.Newf(codes.FailedPrecondition, tr("discovery not found: %s"), id))
continue
}
toolRelease := tool.GetLatestInstalled()
if toolRelease == nil {
statuses = append(statuses, status.Newf(codes.FailedPrecondition, tr("discovery not installed: %s"), id))
continue
}
discoveryPath := toolRelease.InstallDir.Join(tool.Name).String()
d, err := discovery.New(id, discoveryPath)
if err != nil {
statuses = append(statuses, status.Newf(codes.FailedPrecondition, tr("creating discovery: %s"), err))
continue
}
pm.discoveryManager.Add(d)
}

discoveryIDs := discoveryProperties.FirstLevelOf()
delete(discoveryIDs, "required")
// Get the list of tools only if there are discoveries that use Direct discovery integration.
// See:
// https://arduino.github.io/arduino-cli/latest/platform-specification/#pluggable-discovery
// We need the tools only in that case since we might need some tool's
// runtime properties to expand the discovery pattern to run it correctly.
var tools []*cores.ToolRelease
if len(discoveryIDs) > 0 {
var err error
tools, err = pm.FindToolsRequiredFromPlatformRelease(release)
if err != nil {
statuses = append(statuses, status.New(codes.Internal, err.Error()))
}
}

// Handles discovery properties formatted like so:
//
// discovery.DISCOVERY_ID.pattern: "COMMAND_TO_EXECUTE"
for discoveryID, props := range discoveryIDs {
pattern, ok := props.GetOk("pattern")
if !ok {
statuses = append(statuses, status.Newf(codes.FailedPrecondition, tr("can't find pattern for discovery with id %s"), discoveryID))
continue
}
configuration := release.Properties.Clone()
configuration.Merge(release.RuntimeProperties())
configuration.Merge(props)

for _, tool := range tools {
configuration.Merge(tool.RuntimeProperties())
}

cmd := configuration.ExpandPropsInString(pattern)
if cmdArgs, err := properties.SplitQuotedString(cmd, `"'`, true); err != nil {
statuses = append(statuses, status.New(codes.Internal, err.Error()))
} else if d, err := discovery.New(discoveryID, cmdArgs...); err != nil {
statuses = append(statuses, status.New(codes.Internal, err.Error()))
} else {
pm.discoveryManager.Add(d)
}
}

return statuses
}

0 comments on commit ec027a7

Please sign in to comment.