Skip to content

Commit

Permalink
Merge pull request #2501 from posit-dev/sagerb-extract-r-interpreter-…
Browse files Browse the repository at this point in the history
…with-new-active-as-priority

Update agent methods to use the selected R interpreter consistently
  • Loading branch information
sagerb authored Dec 21, 2024
2 parents e72e6cc + 818652c commit 69048f2
Show file tree
Hide file tree
Showing 27 changed files with 1,842 additions and 680 deletions.
3 changes: 2 additions & 1 deletion cmd/publisher/commands/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ func (cmd *DeployCmd) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContext)
return err
}
}
err = initialize.InitIfNeeded(absPath, cmd.ConfigName, ctx.Logger)
i := initialize.NewDefaultInitialize()
err = i.InitIfNeeded(absPath, cmd.ConfigName, ctx.Logger)
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/publisher/commands/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ func (cmd *InitCommand) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContex
if cmd.ConfigName == "" {
cmd.ConfigName = config.DefaultConfigName
}
cfg, err := initialize.Init(absPath, cmd.ConfigName, cmd.Python, cmd.R, ctx.Logger)
i := initialize.NewDefaultInitialize()
cfg, err := i.Init(absPath, cmd.ConfigName, cmd.Python, cmd.R, ctx.Logger)
if err != nil {
return err
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/publisher/commands/redeploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ func (cmd *RedeployCmd) Run(args *cli_types.CommonArgs, ctx *cli_types.CLIContex
}
ctx.Logger = events.NewCLILogger(args.Verbose, os.Stderr)

err = initialize.InitIfNeeded(absPath, cmd.ConfigName, ctx.Logger)
i := initialize.NewDefaultInitialize()
err = i.InitIfNeeded(absPath, cmd.ConfigName, ctx.Logger)
if err != nil {
return err
}
Expand Down
136 changes: 91 additions & 45 deletions internal/initialize/initialize.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,76 @@ import (
"github.com/posit-dev/publisher/internal/config"
"github.com/posit-dev/publisher/internal/inspect"
"github.com/posit-dev/publisher/internal/inspect/detectors"
"github.com/posit-dev/publisher/internal/interpreters"
"github.com/posit-dev/publisher/internal/logging"
"github.com/posit-dev/publisher/internal/util"
)

var ContentDetectorFactory = detectors.NewContentTypeDetector
var PythonInspectorFactory = inspect.NewPythonInspector
var RInspectorFactory = inspect.NewRInspector
type Initialize interface {
Init(base util.AbsolutePath, configName string, python util.Path, rExecutable util.Path, log logging.Logger) (*config.Config, error)
InitIfNeeded(path util.AbsolutePath, configName string, log logging.Logger) error
GetPossibleConfigs(base util.AbsolutePath, python util.Path, rExecutable util.Path, entrypoint util.RelativePath, log logging.Logger) ([]*config.Config, error)

normalizeConfig(cfg *config.Config, base util.AbsolutePath, python util.Path, rExecutable util.Path, entrypoint util.RelativePath, log logging.Logger) error
}

type defaultInitialize struct {
contentTypeDetectorFactory detectors.ContentTypeDetectorFactory
pythonInspectorFactory inspect.PythonInspectorFactory
rInspectorFactory inspect.RInspectorFactory
rInterpreterFactory interpreters.RInterpreterFactory
}

var _ Initialize = &defaultInitialize{}

func NewDefaultInitialize() Initialize {
return NewInitialize(nil, nil, nil, nil)
}

func NewInitialize(
contentTypeDetectorFactoryOverride detectors.ContentTypeDetectorFactory,
pythonInspectorFactoryOverride inspect.PythonInspectorFactory,
rInspectorFactoryOverride inspect.RInspectorFactory,
rInterpreterFactoryOverride interpreters.RInterpreterFactory,
) Initialize {
initialize := &defaultInitialize{
contentTypeDetectorFactory: nil,
pythonInspectorFactory: nil,
rInspectorFactory: nil,
rInterpreterFactory: nil,
}
if contentTypeDetectorFactoryOverride == nil {
initialize.contentTypeDetectorFactory = detectors.NewContentTypeDetector
} else {
initialize.contentTypeDetectorFactory = contentTypeDetectorFactoryOverride
}
if pythonInspectorFactoryOverride == nil {
initialize.pythonInspectorFactory = inspect.NewPythonInspector
} else {
initialize.pythonInspectorFactory = pythonInspectorFactoryOverride
}
if rInspectorFactoryOverride == nil {
initialize.rInspectorFactory = inspect.NewRInspector
} else {
initialize.rInspectorFactory = rInspectorFactoryOverride
}
if rInterpreterFactoryOverride == nil {
initialize.rInterpreterFactory = interpreters.NewRInterpreter
} else {
initialize.rInterpreterFactory = rInterpreterFactoryOverride
}
return initialize
}

var errNoDeployableContent = fmt.Errorf("no deployable content was detected")

const initialComment = ` Configuration file generated by Posit Publisher.
Please review and modify as needed. See the documentation for more options:
https://github.com/posit-dev/publisher/blob/main/docs/configuration.md`

func inspectProject(base util.AbsolutePath, python util.Path, rExecutable util.Path, log logging.Logger) (*config.Config, error) {
func (i *defaultInitialize) inspectProject(base util.AbsolutePath, python util.Path, rExecutable util.Path, log logging.Logger) (*config.Config, error) {
log.Info("Detecting deployment type and entrypoint...", "path", base.String())
typeDetector := ContentDetectorFactory(log)
typeDetector := i.contentTypeDetectorFactory(log)

configs, err := typeDetector.InferType(base, util.RelativePath{})
if err != nil {
Expand All @@ -47,36 +100,44 @@ func inspectProject(base util.AbsolutePath, python util.Path, rExecutable util.P
cfg.Title = base.Base()
}

needPython, err := requiresPython(cfg, base)
needPython, err := i.requiresPython(cfg, base)
if err != nil {
return nil, err
}
if needPython {
inspector := PythonInspectorFactory(base, python, log)
inspector := i.pythonInspectorFactory(base, python, log)
pyConfig, err := inspector.InspectPython()
if err != nil {
return nil, err
}
cfg.Python = pyConfig
}
needR, err := requiresR(cfg, base)

rInspector, err := i.rInspectorFactory(base, rExecutable, log, i.rInterpreterFactory, nil)
if err != nil {
return nil, err
}

needR, err := rInspector.RequiresR(cfg)
if err != nil {
log.Debug("Error while determining R as a requirement", "error", err.Error())
return nil, err
}
if needR {
inspector := RInspectorFactory(base, rExecutable, log)
rConfig, err := inspector.InspectR()
rConfig, err := rInspector.InspectR()
if err != nil {
log.Debug("Error while inspecting to generate an R based configuration", "error", err.Error())
return nil, err
}
cfg.R = rConfig
cfg.Files = append(cfg.Files, fmt.Sprint("/", cfg.R.PackageFile))
}
cfg.Comments = strings.Split(initialComment, "\n")

return cfg, nil
}

func requiresPython(cfg *config.Config, base util.AbsolutePath) (bool, error) {
func (i *defaultInitialize) requiresPython(cfg *config.Config, base util.AbsolutePath) (bool, error) {
if cfg.Python != nil && cfg.Python.Version == "" {
// InferType returned a python configuration for us to fill in.
return true, nil
Expand All @@ -92,27 +153,7 @@ func requiresPython(cfg *config.Config, base util.AbsolutePath) (bool, error) {
return exists, nil
}

func requiresR(cfg *config.Config, base util.AbsolutePath) (bool, error) {
if cfg.R != nil {
// InferType returned an R configuration for us to fill in.
return true, nil
}
if cfg.Type != config.ContentTypeHTML && !cfg.Type.IsPythonContent() {
// Presence of renv.lock implies R is needed,
// unless we're deploying pre-rendered Rmd or Quarto
// (where there will usually be a source file and
// associated lockfile in the directory)
lockfilePath := base.Join(inspect.DefaultRenvLockfile)
exists, err := lockfilePath.Exists()
if err != nil {
return false, err
}
return exists, nil
}
return false, nil
}

func normalizeConfig(
func (i *defaultInitialize) normalizeConfig(
cfg *config.Config,
base util.AbsolutePath,
python util.Path,
Expand Down Expand Up @@ -145,14 +186,14 @@ func normalizeConfig(
log.Debug("Inspector populate files list", "total_files", len(cfg.Files))
}

needPython, err := requiresPython(cfg, base)
needPython, err := i.requiresPython(cfg, base)
if err != nil {
log.Debug("Error while determining Python as a requirement", "error", err.Error())
return err
}
if needPython {
log.Debug("Determined that Python is required")
inspector := PythonInspectorFactory(base, python, log)
inspector := i.pythonInspectorFactory(base, python, log)
pyConfig, err := inspector.InspectPython()
if err != nil {
log.Debug("Error while inspecting to generate a Python based configuration", "error", err.Error())
Expand All @@ -161,14 +202,19 @@ func normalizeConfig(
cfg.Python = pyConfig
cfg.Files = append(cfg.Files, fmt.Sprint("/", cfg.Python.PackageFile))
}
needR, err := requiresR(cfg, base)

rInspector, err := i.rInspectorFactory(base, rExecutable, log, i.rInterpreterFactory, nil)
if err != nil {
log.Debug("Error while creating the RInspector", "error", err.Error())
return err
}
needR, err := rInspector.RequiresR(cfg)
if err != nil {
log.Debug("Error while determining R as a requirement", "error", err.Error())
return err
}
if needR {
inspector := RInspectorFactory(base, rExecutable, log)
rConfig, err := inspector.InspectR()
rConfig, err := rInspector.InspectR()
if err != nil {
log.Debug("Error while inspecting to generate an R based configuration", "error", err.Error())
return err
Expand All @@ -181,38 +227,38 @@ func normalizeConfig(
return nil
}

func GetPossibleConfigs(
func (i *defaultInitialize) GetPossibleConfigs(
base util.AbsolutePath,
python util.Path,
rExecutable util.Path,
entrypoint util.RelativePath,
log logging.Logger) ([]*config.Config, error) {

log.Info("Detecting deployment type and entrypoint...", "path", base.String())
typeDetector := ContentDetectorFactory(log)
typeDetector := i.contentTypeDetectorFactory(log)
configs, err := typeDetector.InferType(base, entrypoint)
if err != nil {
return nil, fmt.Errorf("error detecting content type: %w", err)
}

for _, cfg := range configs {
err = normalizeConfig(cfg, base, python, rExecutable, entrypoint, log)
err = i.normalizeConfig(cfg, base, python, rExecutable, entrypoint, log)
if err != nil {
return nil, err
}
}
return configs, nil
}

func Init(base util.AbsolutePath, configName string, python util.Path, rExecutable util.Path, log logging.Logger) (*config.Config, error) {
func (i *defaultInitialize) Init(base util.AbsolutePath, configName string, python util.Path, rExecutable util.Path, log logging.Logger) (*config.Config, error) {
if configName == "" {
configName = config.DefaultConfigName
}
cfg, err := inspectProject(base, python, rExecutable, log)
cfg, err := i.inspectProject(base, python, rExecutable, log)
if err != nil {
return nil, err
}
err = normalizeConfig(cfg, base, python, rExecutable, util.RelativePath{}, log)
err = i.normalizeConfig(cfg, base, python, rExecutable, util.RelativePath{}, log)
if err != nil {
return nil, err
}
Expand All @@ -225,15 +271,15 @@ func Init(base util.AbsolutePath, configName string, python util.Path, rExecutab
}

// InitIfNeeded runs an auto-initialize if the specified config file does not exist.
func InitIfNeeded(path util.AbsolutePath, configName string, log logging.Logger) error {
func (i *defaultInitialize) InitIfNeeded(path util.AbsolutePath, configName string, log logging.Logger) error {
configPath := config.GetConfigPath(path, configName)
exists, err := configPath.Exists()
if err != nil {
return err
}
if !exists {
log.Info("Configuration file does not exist; creating it", "path", configPath.String())
_, err = Init(path, configName, util.Path{}, util.Path{}, log)
_, err = i.Init(path, configName, util.Path{}, util.Path{}, log)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 69048f2

Please sign in to comment.