blob: 64a8782c53edee6459861e1b05b082bc7023f09b [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package lucicfg
import (
"fmt"
"strings"
"go.chromium.org/luci/common/data/stringset"
"go.chromium.org/luci/common/logging"
"go.starlark.net/starlark"
)
// experiments holds a set of registered experiment IDs and enabled ones.
type experiments struct {
all stringset.Set
enabled stringset.Set
}
// Register adds an experiment ID to the set of known experiments.
//
// Does nothing if such ID has already been registered.
func (exp *experiments) Register(id string) {
if exp.all == nil {
exp.all = stringset.New(1)
}
exp.all.Add(id)
}
// Registered returns a sorted list of all registered experiments.
func (exp *experiments) Registered() []string {
return exp.all.ToSortedSlice()
}
// Enable adds an experiment ID to the set of enabled experiments.
//
// Always succeeds, but returns false if the experiment ID hasn't been
// registered before.
func (exp *experiments) Enable(id string) bool {
if exp.enabled == nil {
exp.enabled = stringset.New(1)
}
exp.enabled.Add(id)
return exp.all.Has(id)
}
// IsEnabled returns true if an experiment has been enabled already.
func (exp *experiments) IsEnabled(id string) bool {
return exp.enabled.Has(id)
}
func init() {
// enable_experiment is used by lucicfg.enable_experiment in lucicfg.star.
declNative("enable_experiment", func(call nativeCall) (starlark.Value, error) {
var id starlark.String
if err := call.unpack(1, &id); err != nil {
return nil, err
}
if expID := id.GoString(); !call.State.experiments.Enable(expID) {
help := "there are no experiments available"
if all := call.State.experiments.Registered(); len(all) != 0 {
quoted := make([]string, len(all))
for i, s := range all {
quoted[i] = fmt.Sprintf("%q", s)
}
help = "available experiments: " + strings.Join(quoted, ", ")
}
logging.Warningf(call.Ctx, "enable_experiment: unknown experiment %q (%s). "+
"It is possible the experiment was retired already, consider removing this call to stop the warning.", expID, help)
}
return starlark.None, nil
})
// register_experiment is used in experiments.star.
declNative("register_experiment", func(call nativeCall) (starlark.Value, error) {
var id starlark.String
if err := call.unpack(1, &id); err != nil {
return nil, err
}
call.State.experiments.Register(id.GoString())
return starlark.None, nil
})
// is_experiment_enabled is used in experiments.star.
declNative("is_experiment_enabled", func(call nativeCall) (starlark.Value, error) {
var id starlark.String
if err := call.unpack(1, &id); err != nil {
return nil, err
}
return starlark.Bool(call.State.experiments.IsEnabled(id.GoString())), nil
})
// list_enabled_experiments lists experiments enabled via enable_experiment.
//
// Lists all experiments passed to enable_experiment(...), even ones that
// aren't registered anymore. This list ends up in `lucicfg {...}` section of
// project.cfg. Listing *all* experiments there is useful to figure out what
// LUCI projects enable retired experiments.
declNative("list_enabled_experiments", func(call nativeCall) (starlark.Value, error) {
if err := call.unpack(0); err != nil {
return nil, err
}
exps := make([]starlark.Value, 0, call.State.experiments.enabled.Len())
for _, exp := range call.State.experiments.enabled.ToSortedSlice() {
exps = append(exps, starlark.String(exp))
}
return starlark.NewList(exps), nil
})
}