diff --git a/commands/instances.go b/commands/instances.go index 102115072e6..0145f3639b5 100644 --- a/commands/instances.go +++ b/commands/instances.go @@ -248,6 +248,11 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor s := &cmderrors.PlatformLoadingError{Cause: err} responseError(s.GRPCStatus()) } + } else if profile.RequireSystemInstalledPlatform() { + for _, err := range pmb.LoadGlobalHardwareForProfile(profile) { + s := &cmderrors.PlatformLoadingError{Cause: err} + responseError(s.GRPCStatus()) + } } else { // Load platforms from profile errs := pmb.LoadHardwareForProfile(ctx, profile, true, downloadCallback, taskCallback, s.settings) diff --git a/docs/sketch-project-file.md b/docs/sketch-project-file.md index 6f66a1f0fa3..661a6a4398f 100644 --- a/docs/sketch-project-file.md +++ b/docs/sketch-project-file.md @@ -28,9 +28,9 @@ profiles: fqbn: programmer: platforms: - - platform: () + - platform: [()] platform_index_url: <3RD_PARTY_PLATFORM_URL> - - platform: () + - platform: [()] platform_index_url: <3RD_PARTY_PLATFORM_DEPENDENCY_URL> libraries: - () @@ -73,6 +73,14 @@ The following fields are available since Arduino CLI 1.1.0: `baudrate: 115200`) but any setting/value can be specified. Multiple settings can be set. These fields are optional. - `` is the protocol for the port used to upload and monitor the board. This field is optional. +#### Using a system-installed platform. + +The fields `` and `` are optional, if they are omitted, the sketch +compilation will use the platforms installed system-wide. This could be helpful during the development of a platform +(where a specific release is not yet available), or if a specific version of a platform is not a strict requirement. + +#### An example of a complete project file. + A complete example of a sketch project file may be the following: ``` diff --git a/internal/arduino/cores/packagemanager/profiles.go b/internal/arduino/cores/packagemanager/profiles.go index d11682b32c0..216b057af18 100644 --- a/internal/arduino/cores/packagemanager/profiles.go +++ b/internal/arduino/cores/packagemanager/profiles.go @@ -33,6 +33,13 @@ import ( "github.com/sirupsen/logrus" ) +// LoadGlobalHardwareForProfile loads the hardware platforms for the given profile. +// It uses the global package manager and does not download or install any missing tools or platforms. +func (pmb *Builder) LoadGlobalHardwareForProfile(p *sketch.Profile) []error { + pmb.profile = p + return pmb.LoadHardware() +} + // LoadHardwareForProfile load the hardware platforms for the given profile. // If installMissing is true then possibly missing tools and platforms will be downloaded and installed. func (pmb *Builder) LoadHardwareForProfile(ctx context.Context, p *sketch.Profile, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, settings *configuration.Settings) []error { diff --git a/internal/arduino/sketch/profiles.go b/internal/arduino/sketch/profiles.go index 1217bb82dec..8592d4294f7 100644 --- a/internal/arduino/sketch/profiles.go +++ b/internal/arduino/sketch/profiles.go @@ -120,6 +120,11 @@ type Profile struct { Libraries ProfileRequiredLibraries `yaml:"libraries"` } +// UsesSystemPlatform checks if this profile requires a system installed platform. +func (p *Profile) RequireSystemInstalledPlatform() bool { + return p.Platforms[0].RequireSystemInstalledPlatform() +} + // ToRpc converts this Profile to an rpc.SketchProfile func (p *Profile) ToRpc() *rpc.SketchProfile { var portConfig *rpc.MonitorPortConfiguration @@ -182,6 +187,20 @@ func (p *ProfileRequiredPlatforms) AsYaml() string { return res } +func (p *ProfileRequiredPlatforms) UnmarshalYAML(unmarshal func(interface{}) error) error { + _p := (*[]*ProfilePlatformReference)(p) + if err := unmarshal(_p); err != nil { + return err + } + requireSystemPlatform := (*_p)[0].RequireSystemInstalledPlatform() + for _, platform := range *_p { + if platform.RequireSystemInstalledPlatform() != requireSystemPlatform { + return errors.New(i18n.Tr("all platforms in a profile must either require a specific version or not")) + } + } + return nil +} + // ProfileRequiredLibraries is a list of ProfileLibraryReference (libraries // required to build the sketch using this profile) type ProfileRequiredLibraries []*ProfileLibraryReference @@ -206,6 +225,12 @@ type ProfilePlatformReference struct { PlatformIndexURL *url.URL } +// RequireSystemInstalledPlatform returns true if the platform reference +// does not specify a version, meaning it requires the system installed platform. +func (p *ProfilePlatformReference) RequireSystemInstalledPlatform() bool { + return p.Version == nil +} + // InternalUniqueIdentifier returns the unique identifier for this object func (p *ProfilePlatformReference) InternalUniqueIdentifier() string { id := p.String() @@ -224,7 +249,12 @@ func (p *ProfilePlatformReference) String() string { // AsYaml outputs the platform reference as Yaml func (p *ProfilePlatformReference) AsYaml() string { - res := fmt.Sprintf(" - platform: %s:%s (%s)\n", p.Packager, p.Architecture, p.Version) + res := "" + if p.Version != nil { + res += fmt.Sprintf(" - platform: %s:%s (%s)\n", p.Packager, p.Architecture, p.Version) + } else { + res += fmt.Sprintf(" - platform: %s:%s\n", p.Packager, p.Architecture) + } if p.PlatformIndexURL != nil { res += fmt.Sprintf(" platform_index_url: %s\n", p.PlatformIndexURL) } @@ -232,12 +262,25 @@ func (p *ProfilePlatformReference) AsYaml() string { } func parseNameAndVersion(in string) (string, string, bool) { - re := regexp.MustCompile(`^([a-zA-Z0-9.\-_ :]+) \((.+)\)$`) - split := re.FindAllStringSubmatch(in, -1) - if len(split) != 1 || len(split[0]) != 3 { - return "", "", false + { + // Try to parse the input string in the format "VENDOR:ARCH (VERSION)" + re := regexp.MustCompile(`^([a-zA-Z0-9.\-_ :]+) \((.+)\)$`) + split := re.FindAllStringSubmatch(in, -1) + if len(split) == 1 && len(split[0]) == 3 { + return split[0][1], split[0][2], true + } + } + + { + // Try to parse the input string in the format "VENDOR:ARCH" + re := regexp.MustCompile(`^([a-zA-Z0-9.\-_ :]+)$`) + split := re.FindAllStringSubmatch(in, -1) + if len(split) == 1 && len(split[0]) == 2 { + return split[0][1], "", true + } } - return split[0][1], split[0][2], true + + return "", "", false } // UnmarshalYAML decodes a ProfilePlatformReference from YAML source. @@ -250,14 +293,23 @@ func (p *ProfilePlatformReference) UnmarshalYAML(unmarshal func(interface{}) err return errors.New(i18n.Tr("missing '%s' directive", "platform")) } else if platformID, platformVersion, ok := parseNameAndVersion(platformID); !ok { return errors.New(i18n.Tr("invalid '%s' directive", "platform")) - } else if c, err := semver.Parse(platformVersion); err != nil { - return fmt.Errorf("%s: %w", i18n.Tr("error parsing version constraints"), err) - } else if split := strings.SplitN(platformID, ":", 2); len(split) != 2 { - return fmt.Errorf("%s: %s", i18n.Tr("invalid platform identifier"), platformID) } else { - p.Packager = split[0] - p.Architecture = split[1] - p.Version = c + var version *semver.Version + if platformVersion != "" { + if v, err := semver.Parse(platformVersion); err != nil { + return fmt.Errorf("%s: %w", i18n.Tr("error parsing version constraints"), err) + } else { + version = v + } + } + + if split := strings.SplitN(platformID, ":", 2); len(split) != 2 { + return fmt.Errorf("%s: %s", i18n.Tr("invalid platform identifier"), platformID) + } else { + p.Packager = split[0] + p.Architecture = split[1] + p.Version = version + } } if rawIndexURL, ok := data["platform_index_url"]; ok { diff --git a/internal/arduino/sketch/profiles_test.go b/internal/arduino/sketch/profiles_test.go index 5be45603914..8fdfbf04955 100644 --- a/internal/arduino/sketch/profiles_test.go +++ b/internal/arduino/sketch/profiles_test.go @@ -39,4 +39,17 @@ func TestProjectFileLoading(t *testing.T) { require.NoError(t, err) require.Equal(t, proj.AsYaml(), string(golden)) } + { + sketchProj := paths.New("testdata", "profiles", "profile_1.yml") + proj, err := LoadProjectFile(sketchProj) + require.NoError(t, err) + golden, err := sketchProj.ReadFile() + require.NoError(t, err) + require.Equal(t, string(golden), proj.AsYaml()) + } + { + sketchProj := paths.New("testdata", "profiles", "bad_profile_1.yml") + _, err := LoadProjectFile(sketchProj) + require.Error(t, err) + } } diff --git a/internal/arduino/sketch/testdata/profiles/bad_profile_1.yml b/internal/arduino/sketch/testdata/profiles/bad_profile_1.yml new file mode 100644 index 00000000000..382b47d5d1e --- /dev/null +++ b/internal/arduino/sketch/testdata/profiles/bad_profile_1.yml @@ -0,0 +1,9 @@ +profiles: + tiny: + notes: Invalid profile mixing versioned and non-versioned platforms. + fqbn: attiny:avr:ATtinyX5:cpu=attiny85,clock=internal16 + platforms: + - platform: attiny:avr + platform_index_url: http://raw.githubusercontent.com/damellis/attiny/ide-1.6.x-boards-manager/package_damellis_attiny_index.json + - platform: arduino:avr (1.8.3) + diff --git a/internal/arduino/sketch/testdata/profiles/profile_1.yml b/internal/arduino/sketch/testdata/profiles/profile_1.yml new file mode 100644 index 00000000000..83ff97f25f4 --- /dev/null +++ b/internal/arduino/sketch/testdata/profiles/profile_1.yml @@ -0,0 +1,12 @@ +profiles: + giga: + fqbn: arduino:mbed_giga:giga + platforms: + - platform: arduino:mbed_giga (4.3.1) + + giga_any: + fqbn: arduino:mbed_giga:giga + platforms: + - platform: arduino:mbed_giga + +default_profile: giga_any