Skip to content
99 changes: 99 additions & 0 deletions commands/service_profile_create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// This file is part of arduino-cli.
//
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package commands

import (
"context"
"errors"
"fmt"

"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/commands/internal/instances"
"github.com/arduino/arduino-cli/internal/arduino/sketch"
"github.com/arduino/arduino-cli/internal/i18n"
"github.com/arduino/arduino-cli/pkg/fqbn"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
)

// ProfileCreate creates a new project file if it does not exist. If a profile name with the associated FQBN is specified,
// it is added to the project.
func (s *arduinoCoreServerImpl) ProfileCreate(ctx context.Context, req *rpc.ProfileCreateRequest) (*rpc.ProfileCreateResponse, error) {
if req.GetProfileName() == "" {
return nil, &cmderrors.MissingProfileError{}
}
if req.GetFqbn() == "" {
return nil, &cmderrors.MissingFQBNError{}
}

// Returns an error if the main file is missing from the sketch so there is no need to check if the path exists
sk, err := sketch.New(paths.New(req.GetSketchPath()))
if err != nil {
return nil, err
}

fqbn, err := fqbn.Parse(req.GetFqbn())
if err != nil {
return nil, &cmderrors.InvalidFQBNError{Cause: err}
}

// Check that the profile name is unique
if profile, _ := sk.GetProfile(req.ProfileName); profile != nil {
return nil, &cmderrors.ProfileAlreadyExitsError{Profile: req.ProfileName}
}

pme, release, err := instances.GetPackageManagerExplorer(req.GetInstance())
if err != nil {
return nil, err
}
defer release()
if pme.Dirty() {
return nil, &cmderrors.InstanceNeedsReinitialization{}
}

// Automatically detect the target platform if it is installed on the user's machine
_, targetPlatform, _, _, _, err := pme.ResolveFQBN(fqbn)
if err != nil {
if targetPlatform == nil {
return nil, &cmderrors.PlatformNotFoundError{
Platform: fmt.Sprintf("%s:%s", fqbn.Vendor, fqbn.Architecture),
Cause: errors.New(i18n.Tr("platform not installed")),
}
}
return nil, &cmderrors.InvalidFQBNError{Cause: err}
}

newProfile := &sketch.Profile{Name: req.GetProfileName(), FQBN: req.GetFqbn()}
// TODO: what to do with the PlatformIndexURL?
newProfile.Platforms = append(newProfile.Platforms, &sketch.ProfilePlatformReference{
Packager: targetPlatform.Platform.Package.Name,
Architecture: targetPlatform.Platform.Architecture,
Version: targetPlatform.Version,
})

sk.Project.Profiles = append(sk.Project.Profiles, newProfile)
if req.DefaultProfile {
sk.Project.DefaultProfile = newProfile.Name
}

projectFilePath := sk.GetProjectPath()
err = projectFilePath.WriteFile([]byte(sk.Project.AsYaml()))
if err != nil {
return nil, err
}

return &rpc.ProfileCreateResponse{}, nil
}
104 changes: 0 additions & 104 deletions commands/service_profile_init.go

This file was deleted.

17 changes: 17 additions & 0 deletions internal/cli/arguments/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (

"github.com/arduino/arduino-cli/internal/cli/instance"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
"go.bug.st/f"
)

Expand Down Expand Up @@ -172,3 +173,19 @@ func GetAvailablePorts(ctx context.Context, srv rpc.ArduinoCoreServiceServer) []
// Transform the data structure for the completion (DetectedPort -> Port)
return f.Map(list.GetPorts(), (*rpc.DetectedPort).GetPort)
}

// GetProfileLibraries is an helper function useful to autocomplete.
// It returns a list of libraries present in the specified profile.
func GetProfileLibraries(ctx context.Context, srv rpc.ArduinoCoreServiceServer, sketchPath *paths.Path, profile string) []string {
resp, err := srv.ProfileLibList(ctx, &rpc.ProfileLibListRequest{
SketchPath: sketchPath.String(),
ProfileName: profile,
})
if err != nil {
return nil
}
res := f.Map(resp.GetLibraries(), func(lib *rpc.ProfileLibraryReference) string {
return lib.GetIndexLibrary().GetName()
})
return res
}
2 changes: 2 additions & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/arduino/arduino-cli/internal/cli/lib"
"github.com/arduino/arduino-cli/internal/cli/monitor"
"github.com/arduino/arduino-cli/internal/cli/outdated"
"github.com/arduino/arduino-cli/internal/cli/profile"
"github.com/arduino/arduino-cli/internal/cli/sketch"
"github.com/arduino/arduino-cli/internal/cli/update"
"github.com/arduino/arduino-cli/internal/cli/updater"
Expand Down Expand Up @@ -162,6 +163,7 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
cmd.AddCommand(burnbootloader.NewCommand(srv))
cmd.AddCommand(version.NewCommand(srv))
cmd.AddCommand(feedback.NewCommand())
cmd.AddCommand(profile.NewCommand(srv))

cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, i18n.Tr("Print the logs on the standard output."))
cmd.Flag("verbose").Hidden = true
Expand Down
45 changes: 45 additions & 0 deletions internal/cli/feedback/result/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1130,6 +1130,12 @@ type ProfileLibraryReference_LocalLibraryResult struct {
Path string `json:"path,omitempty"`
}

func (*ProfileLibraryReference_LocalLibraryResult) isProfileLibraryReference() {}

func (l *ProfileLibraryReference_LocalLibraryResult) String() string {
return fmt.Sprintf("lib: %s", l.Path)
}

func NewProfileLibraryReference_LocalLibraryResult(resp *rpc.ProfileLibraryReference_LocalLibrary) *ProfileLibraryReference_LocalLibraryResult {
return &ProfileLibraryReference_LocalLibraryResult{
Path: resp.GetPath(),
Expand All @@ -1142,10 +1148,49 @@ type ProfileLibraryReference_IndexLibraryResult struct {
IsDependency bool `json:"is_dependency,omitempty"`
}

func (*ProfileLibraryReference_IndexLibraryResult) isProfileLibraryReference() {}

func (l *ProfileLibraryReference_IndexLibraryResult) String() string {
if l.IsDependency {
return fmt.Sprintf("dependency: %s@%s", l.Name, l.Version)
}
return fmt.Sprintf("%s@%s", l.Name, l.Version)
}

func NewProfileLibraryReference_IndexLibraryResult(resp *rpc.ProfileLibraryReference_IndexLibrary) *ProfileLibraryReference_IndexLibraryResult {
return &ProfileLibraryReference_IndexLibraryResult{
Name: resp.GetName(),
Version: resp.GetVersion(),
IsDependency: resp.GetIsDependency(),
}
}

type ProfileLibraryReference struct {
Kind string `json:"kind,omitempty"`
Library ProfileLibraryReference_Library `json:"library,omitempty"`
}

type ProfileLibraryReference_Library interface {
isProfileLibraryReference()
fmt.Stringer
}

func NewProfileLibraryReference(resp *rpc.ProfileLibraryReference) *ProfileLibraryReference {
if lib := resp.GetIndexLibrary(); lib != nil {
return &ProfileLibraryReference{
Library: NewProfileLibraryReference_IndexLibraryResult(lib),
Kind: "index",
}
}
if lib := resp.GetLocalLibrary(); lib != nil {
return &ProfileLibraryReference{
Library: NewProfileLibraryReference_LocalLibraryResult(lib),
Kind: "local",
}
}
return nil
}

func (p *ProfileLibraryReference) String() string {
return p.Library.String()
}
38 changes: 38 additions & 0 deletions internal/cli/profile/profile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// This file is part of arduino-cli.
//
// Copyright 2025 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.

package profile

import (
"os"

"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/spf13/cobra"
)

func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
profileCommand := &cobra.Command{
Use: "profile",
Short: i18n.Tr("Build profile operations."),
Long: i18n.Tr("Build profile operations."),
Example: " " + os.Args[0] + " profile init",
}

profileCommand.AddCommand(initProfileCreateCommand(srv))
profileCommand.AddCommand(initProfileLibCommand(srv))
profileCommand.AddCommand(initProfileSetDefaultCommand(srv))
return profileCommand
}
Loading