Skip to content

Commit ec027a7

Browse files
silvanocerzacmaglieper1234
authored
Add support for Pluggable Discoveries (arduino#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 <accounts@perglass.com> * 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 <accounts@perglass.com> * 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 <accounts@perglass.com> * Fix loading of platform not supporting pluggable discovery * Fix more documentation Co-authored-by: per1234 <accounts@perglass.com> * 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 (arduino#1384) * Update docs/pluggable-discovery-specification.md Co-authored-by: per1234 <accounts@perglass.com> * 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 <c.maglie@arduino.cc> Co-authored-by: per1234 <accounts@perglass.com>
1 parent 3aceff5 commit ec027a7

Some content is hidden

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

75 files changed

+13916
-6213
lines changed

.flake8

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
[flake8]
77
doctests = True
8+
per-file-ignores =
9+
test/test_upload_mock.py:E501
810
ignore =
911
E741,
1012
# W503 and W504 are mutually exclusive. PEP 8 recommends line break before.

arduino/cores/packagemanager/loader.go

Lines changed: 191 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"strings"
2424

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

323-
if platform.Properties.SubTree("discovery").Size() > 0 {
324+
if platform.Properties.SubTree("pluggable_discovery").Size() > 0 {
324325
platform.PluggableDiscoveryAware = true
326+
} else {
327+
platform.Properties.Set("pluggable_discovery.required.0", "builtin:serial-discovery")
328+
platform.Properties.Set("pluggable_discovery.required.1", "builtin:mdns-discovery")
325329
}
326330

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

338342
// Create programmers properties
339343
if programmersProperties, err := properties.SafeLoad(programmersTxtPath.String()); err == nil {
340-
for programmerID, programmerProperties := range programmersProperties.FirstLevelOf() {
341-
platform.Programmers[programmerID] = pm.loadProgrammer(programmerProperties)
344+
for programmerID, programmerProps := range programmersProperties.FirstLevelOf() {
345+
if !platform.PluggableDiscoveryAware {
346+
convertUploadToolsToPluggableDiscovery(programmerProps)
347+
}
348+
platform.Programmers[programmerID] = pm.loadProgrammer(programmerProps)
342349
platform.Programmers[programmerID].PlatformRelease = platform
343350
}
344351
} else {
@@ -349,9 +356,71 @@ func (pm *PackageManager) loadPlatformRelease(platform *cores.PlatformRelease, p
349356
return fmt.Errorf(tr("loading boards: %s"), err)
350357
}
351358

359+
if !platform.PluggableDiscoveryAware {
360+
convertLegacyPlatformToPluggableDiscovery(platform)
361+
}
352362
return nil
353363
}
354364

365+
func convertLegacyPlatformToPluggableDiscovery(platform *cores.PlatformRelease) {
366+
toolsProps := platform.Properties.SubTree("tools").FirstLevelOf()
367+
for toolName, toolProps := range toolsProps {
368+
if !toolProps.ContainsKey("upload.network_pattern") {
369+
continue
370+
}
371+
372+
// Convert network_pattern configuration to pluggable discovery
373+
convertedToolName := toolName + "__pluggable_network"
374+
convertedProps := convertLegacyNetworkPatternToPluggableDiscovery(toolProps, convertedToolName)
375+
376+
// Merge the converted properties in the root configuration
377+
platform.Properties.Merge(convertedProps)
378+
379+
// Add the network upload to the boards using the old method
380+
for _, board := range platform.Boards {
381+
oldUploadTool := board.Properties.Get("upload.tool")
382+
if oldUploadTool == toolName && !board.Properties.ContainsKey("upload.tool.network") {
383+
board.Properties.Set("upload.tool.network", convertedToolName)
384+
385+
// Add identification properties for network protocol
386+
i := 0
387+
for {
388+
if !board.Properties.ContainsKey(fmt.Sprintf("upload_port.%d.vid", i)) {
389+
break
390+
}
391+
i++
392+
}
393+
board.Properties.Set(fmt.Sprintf("upload_port.%d.board", i), board.BoardID)
394+
}
395+
}
396+
}
397+
}
398+
399+
func convertLegacyNetworkPatternToPluggableDiscovery(props *properties.Map, newToolName string) *properties.Map {
400+
pattern, ok := props.GetOk("upload.network_pattern")
401+
if !ok {
402+
return nil
403+
}
404+
props.Remove("upload.network_pattern")
405+
pattern = strings.ReplaceAll(pattern, "{serial.port}", "{upload.port.address}")
406+
pattern = strings.ReplaceAll(pattern, "{network.port}", "{upload.port.properties.port}")
407+
if strings.Contains(pattern, "{network.password}") {
408+
props.Set("upload.field.password", "Password")
409+
props.Set("upload.field.password.secret", "true")
410+
pattern = strings.ReplaceAll(pattern, "{network.password}", "{upload.field.password}")
411+
}
412+
props.Set("upload.pattern", pattern)
413+
414+
prefix := "tools." + newToolName + "."
415+
res := properties.NewMap()
416+
for _, k := range props.Keys() {
417+
v := props.Get(k)
418+
res.Set(prefix+k, v)
419+
// fmt.Println("ADDED:", prefix+k+"="+v)
420+
}
421+
return res
422+
}
423+
355424
func (pm *PackageManager) loadProgrammer(programmerProperties *properties.Map) *cores.Programmer {
356425
return &cores.Programmer{
357426
Name: programmerProperties.Get("name"),
@@ -388,12 +457,6 @@ func (pm *PackageManager) loadBoards(platform *cores.PlatformRelease) error {
388457
// set all other boards properties
389458
delete(propertiesByBoard, "menu")
390459

391-
if !platform.PluggableDiscoveryAware {
392-
for _, boardProperties := range propertiesByBoard {
393-
convertVidPidIdentificationPropertiesToPluggableDiscovery(boardProperties)
394-
}
395-
}
396-
397460
skippedBoards := []string{}
398461
for boardID, boardProperties := range propertiesByBoard {
399462
var board *cores.Board
@@ -412,6 +475,12 @@ func (pm *PackageManager) loadBoards(platform *cores.PlatformRelease) error {
412475
goto next_board
413476
}
414477
}
478+
479+
if !platform.PluggableDiscoveryAware {
480+
convertVidPidIdentificationPropertiesToPluggableDiscovery(boardProperties)
481+
convertUploadToolsToPluggableDiscovery(boardProperties)
482+
}
483+
415484
// The board's ID must be available in a board's properties since it can
416485
// be used in all configuration files for several reasons, like setting compilation
417486
// flags depending on the board id.
@@ -468,6 +537,22 @@ func convertVidPidIdentificationPropertiesToPluggableDiscovery(boardProperties *
468537
}
469538
}
470539

540+
func convertUploadToolsToPluggableDiscovery(props *properties.Map) {
541+
actions := []string{"upload", "bootloader", "program"}
542+
for _, action := range actions {
543+
if !props.ContainsKey(fmt.Sprintf("%s.tool.default", action)) {
544+
tool, found := props.GetOk(fmt.Sprintf("%s.tool", action))
545+
if !found {
546+
// Just skip it, ideally this must never happen but if a platform
547+
// doesn't define an expected upload.tool, bootloader.tool or program.tool
548+
// there will be other issues further down the road after this conversion
549+
continue
550+
}
551+
props.Set(fmt.Sprintf("%s.tool.default", action), tool)
552+
}
553+
}
554+
}
555+
471556
func (pm *PackageManager) loadToolsFromPackage(targetPackage *cores.Package, toolsPath *paths.Path) []*status.Status {
472557
pm.Log.Infof("Loading tools from dir: %s", toolsPath)
473558

@@ -587,3 +672,100 @@ func (pm *PackageManager) LoadToolsFromBundleDirectory(toolsPath *paths.Path) er
587672
}
588673
return nil
589674
}
675+
676+
// LoadDiscoveries load all discoveries for all loaded platforms
677+
// Returns error if:
678+
// * A PluggableDiscovery instance can't be created
679+
// * Tools required by the PlatformRelease cannot be found
680+
// * Command line to start PluggableDiscovery has malformed or mismatched quotes
681+
func (pm *PackageManager) LoadDiscoveries() []*status.Status {
682+
statuses := []*status.Status{}
683+
for _, platform := range pm.InstalledPlatformReleases() {
684+
statuses = append(statuses, pm.loadDiscoveries(platform)...)
685+
}
686+
return statuses
687+
}
688+
689+
func (pm *PackageManager) loadDiscoveries(release *cores.PlatformRelease) []*status.Status {
690+
statuses := []*status.Status{}
691+
discoveryProperties := release.Properties.SubTree("pluggable_discovery")
692+
693+
if discoveryProperties.Size() == 0 {
694+
return nil
695+
}
696+
697+
// Handles discovery properties formatted like so:
698+
//
699+
// Case 1:
700+
// "pluggable_discovery.required": "PLATFORM:DISCOVERY_NAME",
701+
//
702+
// Case 2:
703+
// "pluggable_discovery.required.0": "PLATFORM:DISCOVERY_ID_1",
704+
// "pluggable_discovery.required.1": "PLATFORM:DISCOVERY_ID_2",
705+
//
706+
// If both indexed and unindexed properties are found the unindexed are ignored
707+
for _, id := range discoveryProperties.ExtractSubIndexLists("required") {
708+
tool := pm.GetTool(id)
709+
if tool == nil {
710+
statuses = append(statuses, status.Newf(codes.FailedPrecondition, tr("discovery not found: %s"), id))
711+
continue
712+
}
713+
toolRelease := tool.GetLatestInstalled()
714+
if toolRelease == nil {
715+
statuses = append(statuses, status.Newf(codes.FailedPrecondition, tr("discovery not installed: %s"), id))
716+
continue
717+
}
718+
discoveryPath := toolRelease.InstallDir.Join(tool.Name).String()
719+
d, err := discovery.New(id, discoveryPath)
720+
if err != nil {
721+
statuses = append(statuses, status.Newf(codes.FailedPrecondition, tr("creating discovery: %s"), err))
722+
continue
723+
}
724+
pm.discoveryManager.Add(d)
725+
}
726+
727+
discoveryIDs := discoveryProperties.FirstLevelOf()
728+
delete(discoveryIDs, "required")
729+
// Get the list of tools only if there are discoveries that use Direct discovery integration.
730+
// See:
731+
// https://arduino.github.io/arduino-cli/latest/platform-specification/#pluggable-discovery
732+
// We need the tools only in that case since we might need some tool's
733+
// runtime properties to expand the discovery pattern to run it correctly.
734+
var tools []*cores.ToolRelease
735+
if len(discoveryIDs) > 0 {
736+
var err error
737+
tools, err = pm.FindToolsRequiredFromPlatformRelease(release)
738+
if err != nil {
739+
statuses = append(statuses, status.New(codes.Internal, err.Error()))
740+
}
741+
}
742+
743+
// Handles discovery properties formatted like so:
744+
//
745+
// discovery.DISCOVERY_ID.pattern: "COMMAND_TO_EXECUTE"
746+
for discoveryID, props := range discoveryIDs {
747+
pattern, ok := props.GetOk("pattern")
748+
if !ok {
749+
statuses = append(statuses, status.Newf(codes.FailedPrecondition, tr("can't find pattern for discovery with id %s"), discoveryID))
750+
continue
751+
}
752+
configuration := release.Properties.Clone()
753+
configuration.Merge(release.RuntimeProperties())
754+
configuration.Merge(props)
755+
756+
for _, tool := range tools {
757+
configuration.Merge(tool.RuntimeProperties())
758+
}
759+
760+
cmd := configuration.ExpandPropsInString(pattern)
761+
if cmdArgs, err := properties.SplitQuotedString(cmd, `"'`, true); err != nil {
762+
statuses = append(statuses, status.New(codes.Internal, err.Error()))
763+
} else if d, err := discovery.New(discoveryID, cmdArgs...); err != nil {
764+
statuses = append(statuses, status.New(codes.Internal, err.Error()))
765+
} else {
766+
pm.discoveryManager.Add(d)
767+
}
768+
}
769+
770+
return statuses
771+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy