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

Revert: Build scripts should use buildexpression types. #2788

Merged
merged 1 commit into from
Oct 3, 2023
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
7 changes: 6 additions & 1 deletion internal/runbits/buildscript/buildscript.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ func Sync(proj *project.Project, commitID *strfmt.UUID, out output.Outputer, aut
return false, nil // nothing to do
}
logging.Debug("Merging changes")
expr, err = script.ToBuildExpression()
if err != nil {
return false, errs.Wrap(err, "Unable to translate local build script to build expression")
}

out.Notice(locale.Tl("buildscript_update", "Updating project to reflect build script changes..."))

localCommitID, err := localcommit.Get(proj.Dir())
Expand All @@ -69,7 +74,7 @@ func Sync(proj *project.Project, commitID *strfmt.UUID, out output.Outputer, aut
Owner: proj.Owner(),
Project: proj.Name(),
ParentCommit: localCommitID.String(),
Expression: script.Expr,
Expression: expr,
})
if err != nil {
return false, errs.Wrap(err, "Could not update project to reflect build script changes.")
Expand Down
11 changes: 2 additions & 9 deletions internal/runbits/buildscript/buildscript_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package buildscript

import (
"encoding/json"
"testing"

"github.com/ActiveState/cli/internal/rtutils/ptr"
"github.com/ActiveState/cli/pkg/platform/runtime/buildexpression"
"github.com/ActiveState/cli/pkg/platform/runtime/buildscript"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -31,16 +29,11 @@ in:
runtime`))
require.NoError(t, err)

// Make a copy of the original expression.
bytes, err := json.Marshal(script.Expr)
require.NoError(t, err)
expr, err := buildexpression.New(bytes)
expr, err := script.ToBuildExpression()
require.NoError(t, err)

// Modify the build script.
(*script.Expr.Let.Assignments[0].Value.Ap.Arguments[0].Assignment.Value.List)[0].Str = ptr.To(`77777`)
(*script.Let.Assignments[0].Value.FuncCall.Arguments[0].Assignment.Value.List)[0].Str = ptr.To(`"77777"`)

// Generate the difference between the modified script and the original expression.
result, err := generateDiff(script, expr)
require.NoError(t, err)
assert.Equal(t, `let:
Expand Down
5 changes: 4 additions & 1 deletion internal/runners/pull/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,10 @@ func (p *Pull) mergeBuildScript(strategies *mono_models.MergeStrategies, remoteC
}

// Get the local and remote build expressions to merge.
exprA := script.Expr
exprA, err := script.ToBuildExpression()
if err != nil {
return errs.Wrap(err, "Unable to transform local buildscript into buildexpression")
}
bp := model.NewBuildPlannerModel(p.auth)
exprB, err := bp.GetBuildExpression(p.project.Owner(), p.project.Name(), remoteCommit.String())
if err != nil {
Expand Down
4 changes: 0 additions & 4 deletions pkg/platform/runtime/buildexpression/buildexpression.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,10 +302,6 @@ func newValue(path []string, valueInterface interface{}) (*Value, error) {
case float64:
value.Float = ptr.To(v)

case nil:
// An empty value is interpreted as JSON null.
value.Null = &Null{}

default:
logging.Debug("Unknown type: %T at path %s", v, strings.Join(path, "."))
// An empty value is interpreted as JSON null.
Expand Down
228 changes: 228 additions & 0 deletions pkg/platform/runtime/buildscript/buildexpression.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
package buildscript

import (
"encoding/json"
"sort"
"strings"

"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/multilog"
"github.com/ActiveState/cli/internal/rtutils/ptr"
"github.com/ActiveState/cli/pkg/platform/runtime/buildexpression"
)

const SolveFunction = "solve"
const SolveLegacyFunction = "solve_legacy"
const MergeFunction = "merge"

func NewScriptFromBuildExpression(expr *buildexpression.BuildExpression) (*Script, error) {
data, err := json.Marshal(expr)
if err != nil {
return nil, errs.Wrap(err, "Unable to marshal buildexpression to JSON")
}

m := make(map[string]interface{})
err = json.Unmarshal(data, &m)
if err != nil { // this really should not happen
return nil, errs.Wrap(err, "Could not unmarshal buildexpression")
}

letValue, ok := m["let"]
if !ok {
return nil, errs.New("Build expression has no 'let' key")
}
letMap, ok := letValue.(map[string]interface{})
if !ok {
return nil, errs.New("'let' key is not a JSON object")
}
inValue, ok := letMap["in"]
if !ok {
return nil, errs.New("Build expression's 'let' object has no 'in' key")
}
delete(letMap, "in") // prevent duplication of "in" field when writing the build script

let, err := newLet(letMap)
if err != nil {
return nil, errs.Wrap(err, "Could not parse 'let' key")
}

in, err := newIn(inValue)
if err != nil {
return nil, errs.Wrap(err, "Could not parse 'in' key's value: %v", inValue)
}

return &Script{let, in}, nil
}

func newLet(m map[string]interface{}) (*Let, error) {
assignments, err := newAssignments(m)
if err != nil {
return nil, errs.Wrap(err, "Could not parse 'let' key")
}
return &Let{Assignments: *assignments}, nil
}

func isFunction(name string) bool {
return name == SolveFunction || name == SolveLegacyFunction || name == MergeFunction
}

func newValue(valueInterface interface{}, preferIdent bool) (*Value, error) {
value := &Value{}

switch v := valueInterface.(type) {
case map[string]interface{}:
// Examine keys first to see if this is a function call.
for key := range v {
if isFunction(key) {
f, err := newFuncCall(v)
if err != nil {
return nil, errs.Wrap(err, "Could not parse '%s' function's value: %v", key, v)
}
value.FuncCall = f
}
}

if value.FuncCall == nil {
// It's not a function call, but an object.
object, err := newAssignments(v)
if err != nil {
return nil, errs.Wrap(err, "Could not parse object: %v", v)
}
value.Object = object
}

case []interface{}:
values := []*Value{}
for _, item := range v {
value, err := newValue(item, false)
if err != nil {
return nil, errs.Wrap(err, "Could not parse list: %v", v)
}
values = append(values, value)
}
value.List = &values

case string:
if preferIdent {
value.Ident = &v
} else {
b, err := json.Marshal(v)
if err != nil {
return nil, errs.Wrap(err, "Could not marshal string '%s'", v)
}
value.Str = ptr.To(string(b))
}

case float64:
value.Number = ptr.To(v)

default:
// An empty value is interpreted as JSON null.
value.Null = &Null{}
}

return value, nil
}

func newFuncCall(m map[string]interface{}) (*FuncCall, error) {
// Look in the given object for the function's name and argument object or list.
var name string
var argsInterface interface{}
for key, value := range m {
if isFunction(key) {
name = key
argsInterface = value
break
}
}

args := []*Value{}

switch v := argsInterface.(type) {
case map[string]interface{}:
for key, valueInterface := range v {
value, err := newValue(valueInterface, name == MergeFunction)
if err != nil {
return nil, errs.Wrap(err, "Could not parse '%s' function's argument '%s': %v", name, key, valueInterface)
}
args = append(args, &Value{Assignment: &Assignment{Key: key, Value: value}})
}
sort.SliceStable(args, func(i, j int) bool { return args[i].Assignment.Key < args[j].Assignment.Key })

case []interface{}:
for _, item := range v {
value, err := newValue(item, false)
if err != nil {
return nil, errs.Wrap(err, "Could not parse '%s' function's argument list item: %v", name, item)
}
args = append(args, value)
}

default:
return nil, errs.New("Function '%s' expected to be object or list", name)
}

return &FuncCall{Name: name, Arguments: args}, nil
}

func newAssignments(m map[string]interface{}) (*[]*Assignment, error) {
assignments := []*Assignment{}
for key, valueInterface := range m {
value, err := newValue(valueInterface, false)
if err != nil {
return nil, errs.Wrap(err, "Could not parse '%s' key's value: %v", key, valueInterface)
}
assignments = append(assignments, &Assignment{Key: key, Value: value})
}
sort.SliceStable(assignments, func(i, j int) bool { return assignments[i].Key < assignments[j].Key })
return &assignments, nil
}

func newIn(inValue interface{}) (*In, error) {
in := &In{}

switch v := inValue.(type) {
case map[string]interface{}:
f, err := newFuncCall(v)
if err != nil {
return nil, errs.Wrap(err, "'in' object is not a function call")
}
in.FuncCall = f

case string:
in.Name = ptr.To(strings.TrimPrefix(v, "$"))

default:
return nil, errs.New("'in' value expected to be a function call or string")
}

return in, nil
}

func (s *Script) EqualsBuildExpressionBytes(exprBytes []byte) bool {
expr, err := buildexpression.New(exprBytes)
if err != nil {
multilog.Error("Unable to create buildexpression from incoming JSON: %v", err)
return false
}
return s.EqualsBuildExpression(expr)
}

func (s *Script) EqualsBuildExpression(expr *buildexpression.BuildExpression) bool {
myJson, err := json.Marshal(s)
if err != nil {
multilog.Error("Unable to marshal this buildscript to JSON: %v", err)
return false
}
otherScript, err := NewScriptFromBuildExpression(expr)
if err != nil {
multilog.Error("Unable to transform buildexpression to buildscript: %v", err)
return false
}
otherJson, err := json.Marshal(otherScript)
if err != nil {
multilog.Error("Unable to marshal other buildscript to JSON: %v", err)
return false
}
return string(myJson) == string(otherJson)
}
Loading
Loading