Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 10 additions & 4 deletions internal/captain/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,10 @@ func (p *PackagesValueNoVersion) Type() string {
}

type TimeValue struct {
raw string
Time *time.Time
now bool
raw string
Time *time.Time
now bool
dynamic bool
}

var _ FlagMarshaler = &TimeValue{}
Expand All @@ -271,14 +272,15 @@ func (u *TimeValue) String() string {

func (u *TimeValue) Set(v string) error {
u.raw = v
if v != "now" {
if v != "now" && v != "dynamic" {
tsv, err := time.Parse(time.RFC3339, v)
if err != nil {
return locale.WrapInputError(err, "timeflag_format", "Invalid timestamp: Should be RFC3339 formatted.")
}
u.Time = &tsv
}
u.now = v == "now"
u.dynamic = v == "dynamic"
return nil
}

Expand All @@ -290,6 +292,10 @@ func (u *TimeValue) Now() bool {
return u.now
}

func (u *TimeValue) Dynamic() bool {
return u.dynamic
}

type IntValue struct {
raw string
Int *int
Expand Down
9 changes: 9 additions & 0 deletions internal/runbits/reqop_runbit/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ func UpdateAndReload(prime primeable, script *buildscript.BuildScript, oldCommit
}()
pg = output.StartSpinner(out, locale.T("progress_solve_preruntime"), constants.TerminalAnimationInterval)

if script.Dynamic() {
// Evaluate with dynamic imports first. Then commit.
err := bp.Evaluate(pj.Owner(), pj.Name(), script)
if err != nil {
return errs.Wrap(err, "Unable to dynamically evaluate build expression")
}
script.SetDynamic(false) // StageCommit needs to be called with "solve" node and atTime="now"
}

commitParams := buildplanner.StageCommitParams{
Owner: pj.Owner(),
Project: pj.Name(),
Expand Down
25 changes: 20 additions & 5 deletions internal/runners/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (i *Install) Run(params Params) (rerr error) {
}

// Resolve requirements
reqs, err = i.resolveRequirements(params.Packages, ts, languages)
reqs, err = i.resolveRequirements(params.Packages, ts, languages, params.Timestamp.Dynamic())
if err != nil {
return errs.Wrap(err, "Unable to resolve requirements")
}
Expand All @@ -153,7 +153,7 @@ func (i *Install) Run(params Params) (rerr error) {

// Prepare updated buildscript
script := oldCommit.BuildScript()
if err := prepareBuildScript(script, reqs, ts); err != nil {
if err := prepareBuildScript(script, reqs, ts, params.Timestamp.Dynamic()); err != nil {
return errs.Wrap(err, "Could not prepare build script")
}

Expand Down Expand Up @@ -181,7 +181,7 @@ type errNoMatches struct {
}

// resolveRequirements will attempt to resolve the ingredient and namespace for each requested package
func (i *Install) resolveRequirements(packages captain.PackagesValue, ts time.Time, languages []model.Language) (requirements, error) {
func (i *Install) resolveRequirements(packages captain.PackagesValue, ts time.Time, languages []model.Language, dynamic bool) (requirements, error) {
failed := []*requirement{}
reqs := []*requirement{}
for _, pkg := range packages {
Expand All @@ -191,6 +191,13 @@ func (i *Install) resolveRequirements(packages captain.PackagesValue, ts time.Ti
req.Resolved.Namespace = pkg.Namespace
}

// When using dynamic imports, the packages may not yet exist in the inventory, so searching
// for them is fruitless. Just pass them along.
if dynamic {
reqs = append(reqs, req)
continue
}

// Find ingredients that match the pkg query
ingredients, err := model.SearchIngredientsStrict(pkg.Namespace, pkg.Name, false, false, &ts, i.prime.Auth())
if err != nil {
Expand Down Expand Up @@ -274,7 +281,9 @@ func resolveVersion(req *requirement) error {
}

// Verify that the version provided can be resolved
if versionRe.MatchString(version) {
// Note: if the requirement does not have an ingredient, it is being dynamically imported, so
// we cannot resolve its versions yet.
if versionRe.MatchString(version) && req.Resolved.ingredient != nil {
match := false
for _, knownVersion := range req.Resolved.ingredient.Versions {
if knownVersion.Version == version {
Expand Down Expand Up @@ -340,8 +349,14 @@ func (i *Install) renderUserFacing(reqs requirements) {
i.prime.Output().Notice("")
}

func prepareBuildScript(script *buildscript.BuildScript, requirements requirements, ts time.Time) error {
func prepareBuildScript(script *buildscript.BuildScript, requirements requirements, ts time.Time, dynamic bool) error {
script.SetAtTime(ts, true)

err := script.SetDynamic(dynamic)
if err != nil {
return errs.Wrap(err, "Unable to update solve function")
}

for _, req := range requirements {
requirement := types.Requirement{
Namespace: req.Resolved.Namespace,
Expand Down
19 changes: 19 additions & 0 deletions pkg/buildscript/buildscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type BuildScript struct {

project string
atTime *time.Time
dynamic bool
}

func init() {
Expand Down Expand Up @@ -77,6 +78,24 @@ func (b *BuildScript) SetAtTime(t time.Time, override bool) {
_ = b.atTime
}

func (b *BuildScript) Dynamic() bool {
return b.dynamic
}

func (b *BuildScript) SetDynamic(dynamic bool) error {
b.dynamic = dynamic
solveNode, err := b.getSolveNode()
if err != nil {
return errs.Wrap(err, "Unable to find solve node")
}
if dynamic {
solveNode.FuncCall.Name = solveDynamicFuncName
} else {
solveNode.FuncCall.Name = solveFuncName
}
return nil
}

func (b *BuildScript) Equals(other *BuildScript) (bool, error) {
b2, err := b.Clone()
if err != nil {
Expand Down
15 changes: 8 additions & 7 deletions pkg/buildscript/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import (
)

const (
solveFuncName = "solve"
solveLegacyFuncName = "solve_legacy"
srcKey = "src"
mergeKey = "merge"
requirementsKey = "requirements"
platformsKey = "platforms"
solveFuncName = "solve"
solveLegacyFuncName = "solve_legacy"
solveDynamicFuncName = "dynamic_solve"
srcKey = "src"
mergeKey = "merge"
requirementsKey = "requirements"
platformsKey = "platforms"
)

var errNodeNotFound = errs.New("Could not find node")
Expand Down Expand Up @@ -177,7 +178,7 @@ func getVersionRequirements(v *value) []types.VersionRequirement {
}

func isSolveFuncName(name string) bool {
return name == solveFuncName || name == solveLegacyFuncName
return name == solveFuncName || name == solveLegacyFuncName || name == solveDynamicFuncName
}

func (b *BuildScript) getTargetSolveNode(targets ...string) (*value, error) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/buildscript/unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,5 @@ func Unmarshal(data []byte) (*BuildScript, error) {
atTime = &atTimeVal
}

return &BuildScript{raw, project, atTime}, nil
return &BuildScript{raw: raw, project: project, atTime: atTime}, nil
}
60 changes: 60 additions & 0 deletions pkg/platform/api/buildplanner/request/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package request
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed this file from evaluate.go. All of the content here previously existed and has not changed. As you can see, it calls something called buildCommitTarget.

This PR actually calls evaluate, so that call should live in evaluate.go.


func Build(owner, project, commitId, target string) *build {
return &build{map[string]interface{}{
"organization": owner,
"project": project,
"commitId": commitId,
"target": target,
}}
}

type build struct {
vars map[string]interface{}
}

func (b *build) Query() string {
return `
mutation ($organization: String!, $project: String!, $commitId: String!, $target: String) {
buildCommitTarget(
input: {organization: $organization, project: $project, commitId: $commitId, target: $target}
) {
... on Build {
__typename
status
}
... on Error {
__typename
message
}
... on ErrorWithSubErrors {
__typename
subErrors {
__typename
... on GenericSolveError {
message
isTransient
validationErrors {
error
jsonPath
}
}
... on RemediableSolveError {
message
isTransient
errorType
validationErrors {
error
jsonPath
}
}
}
}
}
}
`
}

func (b *build) Vars() (map[string]interface{}, error) {
return b.vars, nil
}
100 changes: 60 additions & 40 deletions pkg/platform/api/buildplanner/request/evaluate.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
package request
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Despite what it looks like, this file's contents are entirely new. As mentioned in a prior comment, this file already existed, but called something else; it was mis-named, and I've fixed that here.


func Evaluate(owner, project, commitId, target string) *evaluate {
return &evaluate{map[string]interface{}{
"organization": owner,
import (
"time"

"github.com/ActiveState/cli/internal/rtutils/ptr"
)

func Evaluate(organization, project string, expr []byte, atTime *time.Time, dynamic bool, target string) *evaluate {
eval := &evaluate{map[string]interface{}{
"organization": organization,
"project": project,
"commitId": commitId,
"expr": string(expr),
"target": target,
}}

var timestamp *string
if atTime != nil {
timestamp = ptr.To(atTime.Format(time.RFC3339))
}
if !dynamic {
eval.vars["atTime"] = timestamp
} else {
eval.vars["atTime"] = "dynamic"
}

return eval
}

type evaluate struct {
Expand All @@ -15,42 +33,44 @@ type evaluate struct {

func (b *evaluate) Query() string {
return `
mutation ($organization: String!, $project: String!, $commitId: String!, $target: String) {
buildCommitTarget(
input: {organization: $organization, project: $project, commitId: $commitId, target: $target}
) {
... on Build {
__typename
status
}
... on Error {
__typename
message
}
... on ErrorWithSubErrors {
__typename
subErrors {
__typename
... on GenericSolveError {
message
isTransient
validationErrors {
error
jsonPath
}
}
... on RemediableSolveError {
message
isTransient
errorType
validationErrors {
error
jsonPath
}
}
}
}
}
query ($organization: String!, $project: String!, $expr: BuildExpr!, $atTime: AtTime, $target: String) {
project(organization: $organization, project: $project) {
... on Project {
evaluate(expr: $expr, atTime: $atTime, target: $target) {
... on Build {
__typename
status
}
... on Error {
__typename
message
}
... on ErrorWithSubErrors {
__typename
subErrors {
__typename
... on GenericSolveError {
message
isTransient
validationErrors {
error
jsonPath
}
}
... on RemediableSolveError {
message
isTransient
errorType
validationErrors {
error
jsonPath
}
}
}
}
}
}
}
}
`
}
Expand Down
6 changes: 3 additions & 3 deletions pkg/platform/model/buildplanner/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,17 +273,17 @@ func (e ErrFailedArtifacts) Error() string {
func (bp *BuildPlanner) BuildTarget(owner, project, commitID, target string) error {
logging.Debug("BuildTarget, owner: %s, project: %s, commitID: %s, target: %s", owner, project, commitID, target)
resp := &response.BuildResponse{}
err := bp.client.Run(request.Evaluate(owner, project, commitID, target), resp)
err := bp.client.Run(request.Build(owner, project, commitID, target), resp)
if err != nil {
return processBuildPlannerError(err, "Failed to evaluate target")
return processBuildPlannerError(err, "Failed to build target")
}

if resp == nil {
return errs.New("Build is nil")
}

if response.IsErrorResponse(resp.Type) {
return response.ProcessBuildError(resp, "Could not process error response from evaluate target")
return response.ProcessBuildError(resp, "Could not process error response from build target")
}

return nil
Expand Down
Loading
Loading