Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update agent methods to use the selected R interpreter consistently #2501

Merged
merged 29 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d683678
refactor R executable into separate package for re-use, while impleme…
sagerb Dec 17, 2024
9fc2df1
Merge remote-tracking branch 'origin/main' into sagerb-extract-r-inte…
sagerb Dec 17, 2024
3b57cb7
fix linting errors
sagerb Dec 17, 2024
9be66c0
fix unit tests in CI
sagerb Dec 17, 2024
b933e77
trial fix for CI differences
sagerb Dec 17, 2024
20ecfcf
trial fix for CI differences
sagerb Dec 17, 2024
18929be
trial fix for CI differences
sagerb Dec 17, 2024
7d15f48
trial fix for CI differences
sagerb Dec 17, 2024
c66b066
trial fix for CI differences
sagerb Dec 18, 2024
811e4af
wait longer for title prompt in CI e2e test
sagerb Dec 18, 2024
78e4468
lazy load the renv lock file location (and wait to execute `renv` unt…
sagerb Dec 18, 2024
1b1ee2f
handle R executable not found - without unit tests
sagerb Dec 18, 2024
ef9d391
return CI test back to original to see if passes
sagerb Dec 18, 2024
2af2ace
remove temp debug code and adjust unit tests
sagerb Dec 18, 2024
d77b562
additional unit tests
sagerb Dec 19, 2024
4e65fff
reset CI test file
sagerb Dec 19, 2024
c56914d
remove unneeded comment
sagerb Dec 19, 2024
4b79ef2
adjust testability to use dependency injection
sagerb Dec 20, 2024
4389520
remove unneeded comments
sagerb Dec 20, 2024
cddc156
remove unneeded comments
sagerb Dec 20, 2024
7775a57
fix unit test missing mock call
sagerb Dec 20, 2024
12947da
fix unit test missing mock call
sagerb Dec 20, 2024
8964322
fix unit test missing mock call
sagerb Dec 20, 2024
5ee4fa0
fix unit test missing mock call
sagerb Dec 20, 2024
33a4886
debugging occasional vscode-ui test failure in CI
sagerb Dec 20, 2024
0feddff
debugging occasional vscode-ui test failure in CI
sagerb Dec 20, 2024
a28a3d1
debugging occasional vscode-ui test failure in CI
sagerb Dec 20, 2024
ed931fb
debugging occasional vscode-ui test failure in CI
sagerb Dec 20, 2024
818652c
debugging occasional vscode-ui test failure in CI
sagerb Dec 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading