<commit_before>package docker

import (
	"context"
	"fmt"
	"github.com/ViBiOh/dashboard/auth"
	"github.com/ViBiOh/dashboard/jsonHttp"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"gopkg.in/yaml.v2"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"
)

const minMemory = 16777216
const maxMemory = 805306368
const httpPrefix = `http://`
const defaultTag = `:latest`
const deploySuffix = `_deploy`
const networkMode = `traefik`
const traefikHeatlhCheckLabel = `traefik.backend.healthcheck.path`
const traefikPortLabel = `traefik.port`
const linkSeparator = `:`
const waitTime = 30

var imageTag = regexp.MustCompile(`^\S*?:\S+$`)

type dockerComposeService struct {
	Image       string
	Command     []string
	Environment map[string]string
	Labels      map[string]string
	Links       []string
	Ports       []string
	ReadOnly    bool  `yaml:"read_only"`
	CPUShares   int64 `yaml:"cpu_shares"`
	MemoryLimit int64 `yaml:"mem_limit"`
}

type dockerCompose struct {
	Version  string
	Services map[string]dockerComposeService
}

type deployedService struct {
	ID   string
	Name string
}

func getConfig(service *dockerComposeService, user *auth.User, appName string) *container.Config {
	environments := make([]string, len(service.Environment))
	for key, value := range service.Environment {
		environments = append(environments, key+`=`+value)
	}

	if service.Labels == nil {
		service.Labels = make(map[string]string)
	}

	service.Labels[ownerLabel] = user.Username
	service.Labels[appLabel] = appName

	config := container.Config{
		Image:  service.Image,
		Labels: service.Labels,
		Env:    environments,
	}

	if len(service.Command) != 0 {
		config.Cmd = service.Command
	}

	return &config
}

func getHostConfig(service *dockerComposeService) *container.HostConfig {
	hostConfig := container.HostConfig{
		LogConfig: container.LogConfig{Type: `json-file`, Config: map[string]string{
			`max-size`: `50m`,
		}},
		NetworkMode:   networkMode,
		RestartPolicy: container.RestartPolicy{Name: `on-failure`, MaximumRetryCount: 5},
		Resources: container.Resources{
			CPUShares: 128,
			Memory:    minMemory,
		},
		SecurityOpt: []string{`no-new-privileges`},
	}

	if service.ReadOnly {
		hostConfig.ReadonlyRootfs = service.ReadOnly
	}

	if service.CPUShares != 0 {
		hostConfig.Resources.CPUShares = service.CPUShares
	}

	if service.MemoryLimit != 0 {
		if service.MemoryLimit <= maxMemory {
			hostConfig.Resources.Memory = service.MemoryLimit
		} else {
			hostConfig.Resources.Memory = maxMemory
		}
	}

	return &hostConfig
}

func getNetworkConfig(service *dockerComposeService, deployedServices *map[string]deployedService) *network.NetworkingConfig {
	traefikConfig := network.EndpointSettings{}

	for _, link := range service.Links {
		linkParts := strings.Split(link, linkSeparator)

		target := linkParts[0]
		if linkedService, ok := (*deployedServices)[target]; ok {
			target = getFinalName(linkedService.Name)
		}

		alias := linkParts[0]
		if len(linkParts) > 1 {
			alias = linkParts[1]
		}

		traefikConfig.Links = append(traefikConfig.Links, target+linkSeparator+alias)
	}

	return &network.NetworkingConfig{
		EndpointsConfig: map[string]*network.EndpointSettings{
			networkMode: &traefikConfig,
		},
	}
}

func pullImage(image string, user *auth.User) error {
	if !imageTag.MatchString(image) {
		image = image + defaultTag
	}

	log.Print(user.Username + ` starts pulling for ` + image)
	pull, err := docker.ImagePull(context.Background(), image, types.ImagePullOptions{})
	if err != nil {
		return fmt.Errorf(`Error while pulling image: %v`, err)
	}

	readBody(pull)
	log.Print(user.Username + ` ends pulling for ` + image)
	return nil
}

func healthCheckContainers(containers []*types.ContainerJSON) {
	healthCheckSuccess := make(map[string]bool)

	for len(healthCheckSuccess) != len(containers) {
		for _, container := range containers {
			if !healthCheckSuccess[container.ID] && healthCheckContainer(container) {
				healthCheckSuccess[container.ID] = true
			}
		}

		time.Sleep(waitTime * time.Second)
	}
}

func healthCheckContainer(container *types.ContainerJSON) bool {
	if container.Config.Labels[traefikHeatlhCheckLabel] != `` {
		log.Printf(`Checking health of container %s`, container.Name)

		response, err := http.Get(httpPrefix + container.NetworkSettings.Networks[networkMode].IPAddress + container.Config.Labels[traefikPortLabel] + container.Config.Labels[traefikHeatlhCheckLabel])

		if err != nil {
			log.Printf(`Unable to health check for container %s : %v`, container.Name, err)
			return true
		}

		if response.StatusCode != http.StatusOK {
			log.Printf(`Health check failed for container %s : HTTP/%d`, container.Name, response.StatusCode)
			return false
		}
	}

	return true
}

func cleanContainers(containers *[]types.Container, user *auth.User) {
	for _, container := range *containers {
		log.Print(user.Username + ` stops ` + strings.Join(container.Names, `, `))
		stopContainer(container.ID)
		log.Print(user.Username + ` rm ` + strings.Join(container.Names, `, `))
		rmContainer(container.ID)
	}
}

func renameDeployedContainers(containers *map[string]deployedService) error {
	for _, service := range *containers {
		if err := docker.ContainerRename(context.Background(), service.ID, getFinalName(service.Name)); err != nil {
			return fmt.Errorf(`Error while renaming container %s: %v`, service.Name, err)
		}
	}

	return nil
}

func getServiceFullName(app string, service string) string {
	return app + `_` + service + deploySuffix
}

func getFinalName(serviceFullName string) string {
	return strings.TrimSuffix(serviceFullName, deploySuffix)
}

func deleteServices(services map[string]deployedService) {
	for service, container := range services {
		if err := rmContainer(container.ID); err != nil {
			log.Printf(`Error while deleting container for %s : %v`, service, err)
		}
	}
}

func startServices(services map[string]deployedService) {
	for service, container := range services {
		if err := startContainer(container.ID); err != nil {
			log.Printf(`Error while starting container for %s : %v`, service, err)
		}
	}
}

func inspectServices(services map[string]deployedService) []*types.ContainerJSON {
	containers := make([]*types.ContainerJSON, 0, len(services))

	for service, container := range services {
		infos, err := inspectContainer(container.ID)
		if err != nil {
			log.Printf(`Error while inspecting container for %s : %v`, service, err)
		}

		containers = append(containers, &infos)
	}

	return containers
}

func createAppHandler(w http.ResponseWriter, user *auth.User, appName []byte, composeFile []byte) {
	if len(appName) == 0 || len(composeFile) == 0 {
		http.Error(w, `An application name and a compose file are required`, http.StatusBadRequest)
		return
	}

	compose := dockerCompose{}
	if err := yaml.Unmarshal(composeFile, &compose); err != nil {
		errorHandler(w, fmt.Errorf(`Error while unmarshalling compose file: %v`, err))
		return
	}

	appNameStr := string(appName)
	log.Print(user.Username + ` deploys ` + appNameStr)

	ownerContainers, err := listContainers(user, &appNameStr)
	if err != nil {
		errorHandler(w, err)
		return
	}

	deployedServices := make(map[string]deployedService)

	var creationError = false
	for serviceName, service := range compose.Services {
		if err := pullImage(service.Image, user); err != nil {
			errorHandler(w, err)
			return
		}

		serviceFullName := getServiceFullName(appNameStr, serviceName)
		log.Printf(`%s starts %s`, user.Username, serviceFullName)

		createdContainer, err := docker.ContainerCreate(context.Background(), getConfig(&service, user, appNameStr), getHostConfig(&service), getNetworkConfig(&service, &deployedServices), serviceFullName)
		if err != nil {
			errorHandler(w, fmt.Errorf(`Error while creating container: %v`, err))
			creationError = true
			break
		}

		deployedServices[serviceName] = deployedService{ID: createdContainer.ID, Name: serviceFullName}
	}

	if creationError {
		deleteServices(deployedServices)
		return
	}

	startServices(deployedServices)

	go func() {
		log.Printf(`Waiting for new containers to start...`)

		healthCheckContainers(inspectServices(deployedServices))
		cleanContainers(&ownerContainers, user)

		if err := renameDeployedContainers(&deployedServices); err != nil {
			log.Print(err)
		}
	}()

	jsonHttp.ResponseJSON(w, results{deployedServices})
}
<commit_msg>Adding port separator<commit_after>package docker

import (
	"context"
	"fmt"
	"github.com/ViBiOh/dashboard/auth"
	"github.com/ViBiOh/dashboard/jsonHttp"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/api/types/network"
	"gopkg.in/yaml.v2"
	"log"
	"net/http"
	"regexp"
	"strings"
	"time"
)

const minMemory = 16777216
const maxMemory = 805306368
const httpPrefix = `http://`
const portSeparator = `:`
const defaultTag = `:latest`
const deploySuffix = `_deploy`
const networkMode = `traefik`
const traefikHeatlhCheckLabel = `traefik.backend.healthcheck.path`
const traefikPortLabel = `traefik.port`
const linkSeparator = `:`
const waitTime = 30

var imageTag = regexp.MustCompile(`^\S*?:\S+$`)

type dockerComposeService struct {
	Image       string
	Command     []string
	Environment map[string]string
	Labels      map[string]string
	Links       []string
	Ports       []string
	ReadOnly    bool  `yaml:"read_only"`
	CPUShares   int64 `yaml:"cpu_shares"`
	MemoryLimit int64 `yaml:"mem_limit"`
}

type dockerCompose struct {
	Version  string
	Services map[string]dockerComposeService
}

type deployedService struct {
	ID   string
	Name string
}

func getConfig(service *dockerComposeService, user *auth.User, appName string) *container.Config {
	environments := make([]string, len(service.Environment))
	for key, value := range service.Environment {
		environments = append(environments, key+`=`+value)
	}

	if service.Labels == nil {
		service.Labels = make(map[string]string)
	}

	service.Labels[ownerLabel] = user.Username
	service.Labels[appLabel] = appName

	config := container.Config{
		Image:  service.Image,
		Labels: service.Labels,
		Env:    environments,
	}

	if len(service.Command) != 0 {
		config.Cmd = service.Command
	}

	return &config
}

func getHostConfig(service *dockerComposeService) *container.HostConfig {
	hostConfig := container.HostConfig{
		LogConfig: container.LogConfig{Type: `json-file`, Config: map[string]string{
			`max-size`: `50m`,
		}},
		NetworkMode:   networkMode,
		RestartPolicy: container.RestartPolicy{Name: `on-failure`, MaximumRetryCount: 5},
		Resources: container.Resources{
			CPUShares: 128,
			Memory:    minMemory,
		},
		SecurityOpt: []string{`no-new-privileges`},
	}

	if service.ReadOnly {
		hostConfig.ReadonlyRootfs = service.ReadOnly
	}

	if service.CPUShares != 0 {
		hostConfig.Resources.CPUShares = service.CPUShares
	}

	if service.MemoryLimit != 0 {
		if service.MemoryLimit <= maxMemory {
			hostConfig.Resources.Memory = service.MemoryLimit
		} else {
			hostConfig.Resources.Memory = maxMemory
		}
	}

	return &hostConfig
}

func getNetworkConfig(service *dockerComposeService, deployedServices *map[string]deployedService) *network.NetworkingConfig {
	traefikConfig := network.EndpointSettings{}

	for _, link := range service.Links {
		linkParts := strings.Split(link, linkSeparator)

		target := linkParts[0]
		if linkedService, ok := (*deployedServices)[target]; ok {
			target = getFinalName(linkedService.Name)
		}

		alias := linkParts[0]
		if len(linkParts) > 1 {
			alias = linkParts[1]
		}

		traefikConfig.Links = append(traefikConfig.Links, target+linkSeparator+alias)
	}

	return &network.NetworkingConfig{
		EndpointsConfig: map[string]*network.EndpointSettings{
			networkMode: &traefikConfig,
		},
	}
}

func pullImage(image string, user *auth.User) error {
	if !imageTag.MatchString(image) {
		image = image + defaultTag
	}

	log.Print(user.Username + ` starts pulling for ` + image)
	pull, err := docker.ImagePull(context.Background(), image, types.ImagePullOptions{})
	if err != nil {
		return fmt.Errorf(`Error while pulling image: %v`, err)
	}

	readBody(pull)
	log.Print(user.Username + ` ends pulling for ` + image)
	return nil
}

func healthCheckContainers(containers []*types.ContainerJSON) {
	healthCheckSuccess := make(map[string]bool)

	for len(healthCheckSuccess) != len(containers) {
		for _, container := range containers {
			if !healthCheckSuccess[container.ID] && healthCheckContainer(container) {
				healthCheckSuccess[container.ID] = true
			}
		}

		time.Sleep(waitTime * time.Second)
	}
}

func healthCheckContainer(container *types.ContainerJSON) bool {
	if container.Config.Labels[traefikHeatlhCheckLabel] != `` {
		log.Printf(`Checking health of container %s`, container.Name)

		response, err := http.Get(httpPrefix + container.NetworkSettings.Networks[networkMode].IPAddress + portSeparator + container.Config.Labels[traefikPortLabel] + container.Config.Labels[traefikHeatlhCheckLabel])

		if err != nil {
			log.Printf(`Unable to health check for container %s : %v`, container.Name, err)
			return true
		}

		if response.StatusCode != http.StatusOK {
			log.Printf(`Health check failed for container %s : HTTP/%d`, container.Name, response.StatusCode)
			return false
		}
	}

	return true
}

func cleanContainers(containers *[]types.Container, user *auth.User) {
	for _, container := range *containers {
		log.Print(user.Username + ` stops ` + strings.Join(container.Names, `, `))
		stopContainer(container.ID)
		log.Print(user.Username + ` rm ` + strings.Join(container.Names, `, `))
		rmContainer(container.ID)
	}
}

func renameDeployedContainers(containers *map[string]deployedService) error {
	for _, service := range *containers {
		if err := docker.ContainerRename(context.Background(), service.ID, getFinalName(service.Name)); err != nil {
			return fmt.Errorf(`Error while renaming container %s: %v`, service.Name, err)
		}
	}

	return nil
}

func getServiceFullName(app string, service string) string {
	return app + `_` + service + deploySuffix
}

func getFinalName(serviceFullName string) string {
	return strings.TrimSuffix(serviceFullName, deploySuffix)
}

func deleteServices(services map[string]deployedService) {
	for service, container := range services {
		if err := rmContainer(container.ID); err != nil {
			log.Printf(`Error while deleting container for %s : %v`, service, err)
		}
	}
}

func startServices(services map[string]deployedService) {
	for service, container := range services {
		if err := startContainer(container.ID); err != nil {
			log.Printf(`Error while starting container for %s : %v`, service, err)
		}
	}
}

func inspectServices(services map[string]deployedService) []*types.ContainerJSON {
	containers := make([]*types.ContainerJSON, 0, len(services))

	for service, container := range services {
		infos, err := inspectContainer(container.ID)
		if err != nil {
			log.Printf(`Error while inspecting container for %s : %v`, service, err)
		}

		containers = append(containers, &infos)
	}

	return containers
}

func createAppHandler(w http.ResponseWriter, user *auth.User, appName []byte, composeFile []byte) {
	if len(appName) == 0 || len(composeFile) == 0 {
		http.Error(w, `An application name and a compose file are required`, http.StatusBadRequest)
		return
	}

	compose := dockerCompose{}
	if err := yaml.Unmarshal(composeFile, &compose); err != nil {
		errorHandler(w, fmt.Errorf(`Error while unmarshalling compose file: %v`, err))
		return
	}

	appNameStr := string(appName)
	log.Print(user.Username + ` deploys ` + appNameStr)

	ownerContainers, err := listContainers(user, &appNameStr)
	if err != nil {
		errorHandler(w, err)
		return
	}

	deployedServices := make(map[string]deployedService)

	var creationError = false
	for serviceName, service := range compose.Services {
		if err := pullImage(service.Image, user); err != nil {
			errorHandler(w, err)
			return
		}

		serviceFullName := getServiceFullName(appNameStr, serviceName)
		log.Printf(`%s starts %s`, user.Username, serviceFullName)

		createdContainer, err := docker.ContainerCreate(context.Background(), getConfig(&service, user, appNameStr), getHostConfig(&service), getNetworkConfig(&service, &deployedServices), serviceFullName)
		if err != nil {
			errorHandler(w, fmt.Errorf(`Error while creating container: %v`, err))
			creationError = true
			break
		}

		deployedServices[serviceName] = deployedService{ID: createdContainer.ID, Name: serviceFullName}
	}

	if creationError {
		deleteServices(deployedServices)
		return
	}

	startServices(deployedServices)

	go func() {
		log.Printf(`Waiting for new containers to start...`)

		healthCheckContainers(inspectServices(deployedServices))
		cleanContainers(&ownerContainers, user)

		if err := renameDeployedContainers(&deployedServices); err != nil {
			log.Print(err)
		}
	}()

	jsonHttp.ResponseJSON(w, results{deployedServices})
}
<|endoftext|>


<commit_before>package http2

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/xgfone/go-tools/log2"
)

// Render is a HTTP render interface.
type Render interface {
	// Render only writes the body data into the response, which should not
	// write the status code and has no need to set the Content-Type header.
	Render(http.ResponseWriter) error
}

// Context is a wrapper of http.Request and http.ResponseWriter.
//
// Notice: the Context struct refers to github.com/henrylee2cn/faygo and
// github.com/gin-gonic/gin.
type Context struct {
	Request *http.Request
	Writer  http.ResponseWriter

	query url.Values
}

// ContextHandler converts a context handler to http.Handler.
//
// For example,
//
//     func handler(c Context) error {
//          // ...
//     }
//     http.Handle("/", ContextHandler(handler))
func ContextHandler(f func(Context) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if err := f(NewContext(w, r)); err != nil {
			log2.ErrorF("Failed to handle %q: %s", r.RequestURI, err)
		}
	})
}

// NewContext returns a new Context.
func NewContext(w http.ResponseWriter, r *http.Request) Context {
	return Context{
		Request: r,
		Writer:  w,
		query:   r.URL.Query(),
	}
}

// IsWebsocket returns true if the request is websocket.
func (c Context) IsWebsocket() bool {
	if strings.Contains(strings.ToLower(c.GetHeader("Connection")), "upgrade") &&
		strings.ToLower(c.GetHeader("Upgrade")) == "websocket" {
		return true
	}
	return false
}

// ClientIP returns the client ip.
func (c Context) ClientIP() string {
	return ClientIP(c.Request)
}

// Host returns a host:port of the this request from the client.
func (c Context) Host() string {
	return c.Request.Host
}

// Method returns the request method.
func (c Context) Method() string {
	return c.Request.Method
}

// Domain returns the domain of the client.
func (c Context) Domain() string {
	return strings.Split(c.Request.Host, ":")[0]
}

// Path returns the path of the request URL.
func (c Context) Path() string {
	return c.Request.URL.Path
}

// Proxy returns all the proxys.
func (c Context) Proxy() []string {
	if ip := c.GetHeader(XForwardedFor); ip != "" {
		return strings.Split(ip, ",")
	}
	return []string{}
}

// IsMethod returns true if the request method is the given method.
func (c Context) IsMethod(method string) bool {
	return c.Method() == method
}

// IsAjax returns true if the request is a AJAX request.
func (c Context) IsAjax() bool {
	return c.GetHeader(XRequestedWith) == "XMLHttpRequest"
}

// UserAgent returns the request header "UserAgent".
func (c Context) UserAgent() string {
	return c.GetHeader(UserAgent)
}

// ContentType returns the Content-Type header of the request.
func (c Context) ContentType() string {
	return GetContentType(c.Request)
}

// GetRawData returns the raw body data.
func (c Context) GetRawData() ([]byte, error) {
	return GetBody(c.Request)
}

//////////////////////////////////////////////////////////////////////////////
// Get the request Cookie and Set the response Cookie

// Cookie returns the named cookie provided in the request.
//
// It will return http.ErrNoCookie if there is not the named cookie.
func (c Context) Cookie(name string) (string, error) {
	cookie, err := c.Request.Cookie(name)
	if err != nil {
		return "", err
	}
	return url.QueryUnescape(cookie.Value)
}

// SetCookie adds a Set-Cookie header into the response header.
//
// If the cookie is invalid, it will be dropped silently.
func (c Context) SetCookie(name, value, path, domain string, maxAge int, secure,
	httpOnly bool) {
	if path == "" {
		path = "/"
	}
	http.SetCookie(c.Writer, &http.Cookie{
		Name:     name,
		Value:    url.QueryEscape(value),
		MaxAge:   maxAge,
		Path:     path,
		Domain:   domain,
		Secure:   secure,
		HttpOnly: httpOnly,
	})
}

//////////////////////////////////////////////////////////////////////////////
// URL Query

// GetQuerys returns all query values for the given key.
//
// It will return nil if not the key.
func (c Context) GetQuerys(key string) []string {
	return c.query[key]
}

// GetQuery returns the first query value for the given key.
//
// It will return "" if not the key.
func (c Context) GetQuery(key string) string {
	if vs := c.GetQuerys(key); len(vs) > 0 {
		return vs[0]
	}
	return ""
}

// GetQueryWithDefault is equal to GetQuery, but returns the default if not
// the key.
func (c Context) GetQueryWithDefault(key, _default string) string {
	if v := c.GetQuery(key); v != "" {
		return v
	}
	return _default
}

//////////////////////////////////////////////////////////////////////////////
// Get the request header and Set the response header.

// GetHeader returns the request header by the key.
func (c Context) GetHeader(key string) string {
	return c.Request.Header.Get(key)
}

// SetHeader will set the response header if value is not empty,
// Or delete the response header by the key.
//
// Notice: if key is "", ignore it.
func (c Context) SetHeader(key, value string) {
	if key == "" {
		return
	}

	if value == "" {
		c.Writer.Header().Del(key)
	} else {
		c.Writer.Header().Set(key, value)
	}
}

/////////////////////////////////////////////////////////////////////////////
// Render the response

// Status writes the response header with the status code.
func (c Context) Status(code int) {
	c.Writer.WriteHeader(code)
}

// Redirect redirects the request to location.
//
// code must be betwwen 300 and 308, that's [300, 308], or return an error.
func (c Context) Redirect(code int, location string) error {
	if code < 300 || code > 308 {
		return fmt.Errorf("Cannot redirect with status code %d", code)
	}
	if location == "" {
		location = "/"
	}
	http.Redirect(c.Writer, c.Request, location, code)
	return nil
}

// Error renders the error information to the response body.
//
// if having no second argument, the status code is 500.
func (c Context) Error(err error, code ...int) error {
	status := 500
	if len(code) > 0 {
		status = code[0]
	}
	return c.String(status, "%s", err)
}

// File Sends the file to the client.
func (c Context) File(filepath string) {
	http.ServeFile(c.Writer, c.Request, filepath)
}

// Data writes some data into the repsonse body, with a status code.
func (c Context) Data(code int, contentType string, data []byte) error {
	return Bytes(c.Writer, code, contentType, data)
}

// Render renders the content into the response body, with a status code.
func (c Context) Render(code int, contentType string, r Render) error {
	c.Status(code)
	SetContentType(c.Writer, contentType)
	return r.Render(c.Writer)
}

// String renders the format string into the response body, with a status code.
func (c Context) String(code int, format string, args ...interface{}) error {
	return String(c.Writer, code, format, args...)
}

// XML renders the XML into the response body, with a status code.
func (c Context) XML(code int, v interface{}) error {
	return XML(c.Writer, code, v)
}

// JSON renders the JSON into the response body, with a status code.
func (c Context) JSON(code int, v interface{}) error {
	return JSON(c.Writer, code, v)
}
<commit_msg>Add a returned value for Status in Context<commit_after>package http2

import (
	"fmt"
	"net/http"
	"net/url"
	"strings"

	"github.com/xgfone/go-tools/log2"
)

// Render is a HTTP render interface.
type Render interface {
	// Render only writes the body data into the response, which should not
	// write the status code and has no need to set the Content-Type header.
	Render(http.ResponseWriter) error
}

// Context is a wrapper of http.Request and http.ResponseWriter.
//
// Notice: the Context struct refers to github.com/henrylee2cn/faygo and
// github.com/gin-gonic/gin.
type Context struct {
	Request *http.Request
	Writer  http.ResponseWriter

	query url.Values
}

// ContextHandler converts a context handler to http.Handler.
//
// For example,
//
//     func handler(c Context) error {
//          // ...
//     }
//     http.Handle("/", ContextHandler(handler))
func ContextHandler(f func(Context) error) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if err := f(NewContext(w, r)); err != nil {
			log2.ErrorF("Failed to handle %q: %s", r.RequestURI, err)
		}
	})
}

// NewContext returns a new Context.
func NewContext(w http.ResponseWriter, r *http.Request) Context {
	return Context{
		Request: r,
		Writer:  w,
		query:   r.URL.Query(),
	}
}

// IsWebsocket returns true if the request is websocket.
func (c Context) IsWebsocket() bool {
	if strings.Contains(strings.ToLower(c.GetHeader("Connection")), "upgrade") &&
		strings.ToLower(c.GetHeader("Upgrade")) == "websocket" {
		return true
	}
	return false
}

// ClientIP returns the client ip.
func (c Context) ClientIP() string {
	return ClientIP(c.Request)
}

// Host returns a host:port of the this request from the client.
func (c Context) Host() string {
	return c.Request.Host
}

// Method returns the request method.
func (c Context) Method() string {
	return c.Request.Method
}

// Domain returns the domain of the client.
func (c Context) Domain() string {
	return strings.Split(c.Request.Host, ":")[0]
}

// Path returns the path of the request URL.
func (c Context) Path() string {
	return c.Request.URL.Path
}

// Proxy returns all the proxys.
func (c Context) Proxy() []string {
	if ip := c.GetHeader(XForwardedFor); ip != "" {
		return strings.Split(ip, ",")
	}
	return []string{}
}

// IsMethod returns true if the request method is the given method.
func (c Context) IsMethod(method string) bool {
	return c.Method() == method
}

// IsAjax returns true if the request is a AJAX request.
func (c Context) IsAjax() bool {
	return c.GetHeader(XRequestedWith) == "XMLHttpRequest"
}

// UserAgent returns the request header "UserAgent".
func (c Context) UserAgent() string {
	return c.GetHeader(UserAgent)
}

// ContentType returns the Content-Type header of the request.
func (c Context) ContentType() string {
	return GetContentType(c.Request)
}

// GetRawData returns the raw body data.
func (c Context) GetRawData() ([]byte, error) {
	return GetBody(c.Request)
}

//////////////////////////////////////////////////////////////////////////////
// Get the request Cookie and Set the response Cookie

// Cookie returns the named cookie provided in the request.
//
// It will return http.ErrNoCookie if there is not the named cookie.
func (c Context) Cookie(name string) (string, error) {
	cookie, err := c.Request.Cookie(name)
	if err != nil {
		return "", err
	}
	return url.QueryUnescape(cookie.Value)
}

// SetCookie adds a Set-Cookie header into the response header.
//
// If the cookie is invalid, it will be dropped silently.
func (c Context) SetCookie(name, value, path, domain string, maxAge int, secure,
	httpOnly bool) {
	if path == "" {
		path = "/"
	}
	http.SetCookie(c.Writer, &http.Cookie{
		Name:     name,
		Value:    url.QueryEscape(value),
		MaxAge:   maxAge,
		Path:     path,
		Domain:   domain,
		Secure:   secure,
		HttpOnly: httpOnly,
	})
}

//////////////////////////////////////////////////////////////////////////////
// URL Query

// GetQuerys returns all query values for the given key.
//
// It will return nil if not the key.
func (c Context) GetQuerys(key string) []string {
	return c.query[key]
}

// GetQuery returns the first query value for the given key.
//
// It will return "" if not the key.
func (c Context) GetQuery(key string) string {
	if vs := c.GetQuerys(key); len(vs) > 0 {
		return vs[0]
	}
	return ""
}

// GetQueryWithDefault is equal to GetQuery, but returns the default if not
// the key.
func (c Context) GetQueryWithDefault(key, _default string) string {
	if v := c.GetQuery(key); v != "" {
		return v
	}
	return _default
}

//////////////////////////////////////////////////////////////////////////////
// Get the request header and Set the response header.

// GetHeader returns the request header by the key.
func (c Context) GetHeader(key string) string {
	return c.Request.Header.Get(key)
}

// SetHeader will set the response header if value is not empty,
// Or delete the response header by the key.
//
// Notice: if key is "", ignore it.
func (c Context) SetHeader(key, value string) {
	if key == "" {
		return
	}

	if value == "" {
		c.Writer.Header().Del(key)
	} else {
		c.Writer.Header().Set(key, value)
	}
}

/////////////////////////////////////////////////////////////////////////////
// Render the response

// Status writes the response header with the status code.
//
// The returned value is nil forever.
func (c Context) Status(code int) error {
	c.Writer.WriteHeader(code)
	return nil
}

// Redirect redirects the request to location.
//
// code must be betwwen 300 and 308, that's [300, 308], or return an error.
func (c Context) Redirect(code int, location string) error {
	if code < 300 || code > 308 {
		return fmt.Errorf("Cannot redirect with status code %d", code)
	}
	if location == "" {
		location = "/"
	}
	http.Redirect(c.Writer, c.Request, location, code)
	return nil
}

// Error renders the error information to the response body.
//
// if having no second argument, the status code is 500.
func (c Context) Error(err error, code ...int) error {
	status := 500
	if len(code) > 0 {
		status = code[0]
	}
	return c.String(status, "%s", err)
}

// File Sends the file to the client.
func (c Context) File(filepath string) {
	http.ServeFile(c.Writer, c.Request, filepath)
}

// Data writes some data into the repsonse body, with a status code.
func (c Context) Data(code int, contentType string, data []byte) error {
	return Bytes(c.Writer, code, contentType, data)
}

// Render renders the content into the response body, with a status code.
func (c Context) Render(code int, contentType string, r Render) error {
	c.Status(code)
	SetContentType(c.Writer, contentType)
	return r.Render(c.Writer)
}

// String renders the format string into the response body, with a status code.
func (c Context) String(code int, format string, args ...interface{}) error {
	return String(c.Writer, code, format, args...)
}

// XML renders the XML into the response body, with a status code.
func (c Context) XML(code int, v interface{}) error {
	return XML(c.Writer, code, v)
}

// JSON renders the JSON into the response body, with a status code.
func (c Context) JSON(code int, v interface{}) error {
	return JSON(c.Writer, code, v)
}
<|endoftext|>


<commit_before>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 builder

import (
	"os"
	"path/filepath"

	"mynewt.apache.org/newt/newt/pkg"
	"mynewt.apache.org/newt/newt/target"
	"mynewt.apache.org/newt/newt/toolchain"
	"mynewt.apache.org/newt/util"
)

type Builder struct {
	Packages map[*pkg.LocalPackage]*BuildPackage
	features map[string]bool
	apis     map[string]*BuildPackage

	appPkg       *BuildPackage
	Bsp          *pkg.BspPackage
	compilerPkg  *pkg.LocalPackage
	compilerInfo *toolchain.CompilerInfo

	target *target.Target
}

func NewBuilder(target *target.Target) (*Builder, error) {
	b := &Builder{}

	if err := b.Init(target); err != nil {
		return nil, err
	}

	return b, nil
}

func (b *Builder) Init(target *target.Target) error {
	b.target = target

	b.Packages = map[*pkg.LocalPackage]*BuildPackage{}
	b.features = map[string]bool{}
	b.apis = map[string]*BuildPackage{}

	return nil
}

func (b *Builder) Features() map[string]bool {
	return b.features
}

func (b *Builder) AddFeature(feature string) {
	b.features[feature] = true
}

func (b *Builder) AddPackage(npkg *pkg.LocalPackage) *BuildPackage {
	// Don't allow nil entries to the map
	if npkg == nil {
		panic("Cannot add nil package builder map")
	}

	bpkg := b.Packages[npkg]
	if bpkg == nil {
		bpkg = NewBuildPackage(npkg)
		b.Packages[npkg] = bpkg
	}

	return bpkg
}

// @return bool                 true if this is a new API.
func (b *Builder) AddApi(apiString string, bpkg *BuildPackage) bool {
	curBpkg := b.apis[apiString]
	if curBpkg == nil {
		b.apis[apiString] = bpkg
		return true
	} else {
		if curBpkg != bpkg {
			util.StatusMessage(util.VERBOSITY_QUIET,
				"Warning: API conflict: %s <-> %s\n", curBpkg.Name(),
				bpkg.Name())
		}
		return false
	}
}

func (b *Builder) loadDeps() error {
	// Circularly resolve dependencies, identities, APIs, and required APIs
	// until no new ones exist.
	for {
		reprocess := false
		for _, bpkg := range b.Packages {
			newDeps, newFeatures, err := bpkg.Resolve(b)
			if err != nil {
				return err
			}

			if newFeatures {
				// A new supported feature was discovered.  It is impossible to
				// determine what new dependency and API requirements are
				// generated as a result.  All packages need to be reprocessed.
				for _, bpkg := range b.Packages {
					bpkg.depsResolved = false
					bpkg.apisSatisfied = false
				}
				reprocess = true
				break
			}
			if newDeps {
				reprocess = true
			}
		}

		if !reprocess {
			break
		}
	}

	return nil
}

// Recursively compiles all the .c and .s files in the specified directory.
// Architecture-specific files are also compiled.
func buildDir(srcDir string, c *toolchain.Compiler, arch string,
	ignDirs []string) error {

	// Quietly succeed if the source directory doesn't exist.
	if util.NodeNotExist(srcDir) {
		return nil
	}

	util.StatusMessage(util.VERBOSITY_VERBOSE,
		"compiling src in base directory: %s\n", srcDir)

	// Start from the source directory.
	if err := os.Chdir(srcDir); err != nil {
		return util.NewNewtError(err.Error())
	}

	// Don't recurse into destination directories.
	ignDirs = append(ignDirs, "obj")
	ignDirs = append(ignDirs, "bin")

	// Ignore architecture-specific source files for now.  Use a temporary
	// string slice here so that the "arch" directory is not ignored in the
	// subsequent architecture-specific compile phase.
	if err := c.RecursiveCompile(toolchain.COMPILER_TYPE_C,
		append(ignDirs, "arch")); err != nil {

		return err
	}

	archDir := srcDir + "/arch/" + arch + "/"
	util.StatusMessage(util.VERBOSITY_VERBOSE,
		"compiling architecture specific src pkgs in directory: %s\n",
		archDir)

	if util.NodeExist(archDir) {
		if err := os.Chdir(archDir); err != nil {
			return util.NewNewtError(err.Error())
		}
		if err := c.RecursiveCompile(toolchain.COMPILER_TYPE_C,
			ignDirs); err != nil {

			return err
		}

		// compile assembly sources in recursive compile as well
		if err := c.RecursiveCompile(toolchain.COMPILER_TYPE_ASM,
			ignDirs); err != nil {

			return err
		}
	}

	return nil
}

func (b *Builder) newCompiler(bpkg *BuildPackage, dstDir string) (*toolchain.Compiler, error) {
	c, err := toolchain.NewCompiler(b.compilerPkg.BasePath(), dstDir,
		b.target.BuildProfile)
	if err != nil {
		return nil, err
	}
	c.AddInfo(b.compilerInfo)

	if bpkg != nil {
		ci, err := bpkg.CompilerInfo(b)
		if err != nil {
			return nil, err
		}
		c.AddInfo(ci)
	}

	// Specify all the source yml files as dependencies.  If a yml file has
	// changed, a full rebuild is required.
	for _, bp := range b.Packages {
		c.AddDeps(bp.CfgFilenames()...)
	}

	return c, nil
}

// Compiles and archives a package.
func (b *Builder) buildPackage(bpkg *BuildPackage) error {
	srcDir := bpkg.BasePath() + "/src"
	if util.NodeNotExist(srcDir) {
		// Nothing to compile.
		return nil
	}

	c, err := b.newCompiler(bpkg, b.PkgBinDir(bpkg.Name()))
	if err != nil {
		return err
	}

	// Build the package source in two phases:
	// 1. Non-test code.
	// 2. Test code (if the "test" feature is enabled).
	//
	// This is done in two passes because the structure of
	// architecture-specific directories is different for normal code and test
	// code, and not easy to generalize into a single operation:
	//     * src/arch/<target-arch>
	//     * src/test/arch/<target-arch>
	if err = buildDir(srcDir, c, b.Bsp.Arch, []string{"test"}); err != nil {
		return err
	}
	if b.features["TEST"] {
		testSrcDir := srcDir + "/test"
		if err = buildDir(testSrcDir, c, b.Bsp.Arch, nil); err != nil {
			return err
		}
	}

	// Create a static library ("archive").
	if err := os.Chdir(bpkg.BasePath() + "/"); err != nil {
		return util.NewNewtError(err.Error())
	}
	archiveFile := b.ArchivePath(bpkg.Name())
	if err = c.CompileArchive(archiveFile); err != nil {
		return err
	}

	return nil
}

func (b *Builder) link(elfName string) error {
	c, err := b.newCompiler(b.appPkg, b.PkgBinDir(elfName))
	if err != nil {
		return err
	}

	pkgNames := []string{}
	for _, bpkg := range b.Packages {
		archivePath := b.ArchivePath(bpkg.Name())
		if util.NodeExist(archivePath) {
			pkgNames = append(pkgNames, archivePath)
		}
	}

	if b.Bsp.LinkerScript != "" {
		c.LinkerScript = b.Bsp.BasePath() + b.Bsp.LinkerScript
	}
	err = c.CompileElf(elfName, pkgNames)
	if err != nil {
		return err
	}

	return nil
}

// Populates the builder with all the packages that need to be built and
// configures each package's build settings.  After this function executes,
// packages are ready to be built.
func (b *Builder) PrepBuild() error {
	if b.Bsp != nil {
		// Already prepped
		return nil
	}

	// Collect the seed packages.
	bspPkg := b.target.Bsp()
	if bspPkg == nil {
		if b.target.BspName == "" {
			return util.NewNewtError("BSP package not specified by target")
		} else {
			return util.NewNewtError("BSP package not found: " +
				b.target.BspName)
		}
	}

	b.Bsp = pkg.NewBspPackage(bspPkg)
	compilerPkg := b.resolveCompiler()
	if compilerPkg == nil {
		if b.Bsp.CompilerName == "" {
			return util.NewNewtError("Compiler package not specified by BSP")
		} else {
			return util.NewNewtError("Compiler package not found: " +
				b.Bsp.CompilerName)
		}
	}

	// An app package is not required (e.g., unit tests).
	appPkg := b.target.App()

	// Seed the builder with the app (if present), bsp, and target packages.

	var appBpkg *BuildPackage
	if appPkg != nil {
		appBpkg = b.Packages[appPkg]
		if appBpkg == nil {
			appBpkg = b.AddPackage(appPkg)
		}
		b.appPkg = appBpkg
	}

	bspBpkg := b.Packages[bspPkg]
	if bspBpkg == nil {
		bspBpkg = b.AddPackage(bspPkg)
	}

	targetBpkg := b.AddPackage(b.target.Package())

	// Populate the full set of packages to be built and resolve the feature
	// set.
	if err := b.loadDeps(); err != nil {
		return err
	}

	b.logDepInfo()

	// Terminate if any package has an unmet API requirement.
	if err := b.verifyApisSatisfied(); err != nil {
		return err
	}

	// Populate the base set of compiler flags.  Flags from the following
	// packages get applied to every source file:
	//     * app (if present)
	//     * bsp
	//     * compiler (not added here)
	//     * target

	baseCi := toolchain.NewCompilerInfo()

	// App flags.
	if appBpkg != nil {
		appCi, err := appBpkg.CompilerInfo(b)
		if err != nil {
			return err
		}
		baseCi.AddCompilerInfo(appCi)
	}

	// Bsp flags.
	bspCi, err := bspBpkg.CompilerInfo(b)
	if err != nil {
		return err
	}
	baseCi.AddCompilerInfo(bspCi)

	// Target flags.
	targetCi, err := targetBpkg.CompilerInfo(b)
	if err != nil {
		return err
	}

	// Define a cpp symbol indicating the target architecture.
	// XXX: This should probably happen in the bsp after we move the arch field
	// from target to bsp.
	targetCi.Cflags = append(targetCi.Cflags, "-DARCH_"+b.Bsp.Arch)

	baseCi.AddCompilerInfo(targetCi)

	// Note: Compiler flags get added when compiler is created.

	// Read the BSP configuration.  These settings are necessary for the link
	// step.
	if err := b.Bsp.Reload(b.Features()); err != nil {
		return err
	}

	b.compilerPkg = compilerPkg
	b.compilerInfo = baseCi

	return nil
}

func (b *Builder) Build() error {
	if err := b.target.Validate(true); err != nil {
		return err
	}

	// Populate the package and feature sets and calculate the base compiler
	// flags.
	if err := b.PrepBuild(); err != nil {
		return err
	}

	// XXX: If any yml files have changed, a full rebuild is required.  We
	// don't currently check this.

	for _, bpkg := range b.Packages {
		if err := b.buildPackage(bpkg); err != nil {
			return err
		}
	}

	if err := b.link(b.AppElfPath()); err != nil {
		return err
	}

	return nil
}

func (b *Builder) Test(p *pkg.LocalPackage) error {
	if err := b.target.Validate(false); err != nil {
		return err
	}

	// Seed the builder with the package under test.
	testBpkg := b.AddPackage(p)

	// A few features are automatically supported when the test command is
	// used:
	//     * TEST:      ensures that the test code gets compiled.
	//     * SELFTEST:  indicates that there is no app.
	b.AddFeature("TEST")
	b.AddFeature("SELFTEST")

	// Populate the package and feature sets and calculate the base compiler
	// flags.
	err := b.PrepBuild()
	if err != nil {
		return err
	}

	// Define the PKG_TEST symbol while the package under test is being
	// compiled.  This symbol enables the appropriate main function that
	// usually comes from an app.
	testPkgCi, err := testBpkg.CompilerInfo(b)
	if err != nil {
		return err
	}
	testPkgCi.Cflags = append(testPkgCi.Cflags, "-DMYNEWT_SELFTEST")

	// XXX: If any yml files have changed, a full rebuild is required.  We
	// don't currently check this.

	for _, bpkg := range b.Packages {
		err = b.buildPackage(bpkg)
		if err != nil {
			return err
		}
	}

	testFilename := b.TestExePath(p.Name())
	err = b.link(testFilename)
	if err != nil {
		return err
	}

	// Run the tests.
	util.StatusMessage(util.VERBOSITY_DEFAULT, "Testing package %s\n", p.Name())

	if err := os.Chdir(filepath.Dir(testFilename)); err != nil {
		return err
	}

	o, err := util.ShellCommand(testFilename)
	if err != nil {
		util.StatusMessage(util.VERBOSITY_DEFAULT, "%s", string(o))

		return util.NewNewtError("Test crashed: " + testFilename)
	}

	util.StatusMessage(util.VERBOSITY_VERBOSE, "%s", string(o))
	util.StatusMessage(util.VERBOSITY_DEFAULT, "Test %s ok!\n", testFilename)

	return nil
}

func (b *Builder) Clean() error {
	path := b.BinDir()
	util.StatusMessage(util.VERBOSITY_VERBOSE, "Cleaning directory %s\n", path)
	err := os.RemoveAll(path)
	return err
}
<commit_msg>Add -DARCH_x cflag to bsp (was: target)<commit_after>/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 builder

import (
	"os"
	"path/filepath"

	"mynewt.apache.org/newt/newt/pkg"
	"mynewt.apache.org/newt/newt/target"
	"mynewt.apache.org/newt/newt/toolchain"
	"mynewt.apache.org/newt/util"
)

type Builder struct {
	Packages map[*pkg.LocalPackage]*BuildPackage
	features map[string]bool
	apis     map[string]*BuildPackage

	appPkg       *BuildPackage
	Bsp          *pkg.BspPackage
	compilerPkg  *pkg.LocalPackage
	compilerInfo *toolchain.CompilerInfo

	target *target.Target
}

func NewBuilder(target *target.Target) (*Builder, error) {
	b := &Builder{}

	if err := b.Init(target); err != nil {
		return nil, err
	}

	return b, nil
}

func (b *Builder) Init(target *target.Target) error {
	b.target = target

	b.Packages = map[*pkg.LocalPackage]*BuildPackage{}
	b.features = map[string]bool{}
	b.apis = map[string]*BuildPackage{}

	return nil
}

func (b *Builder) Features() map[string]bool {
	return b.features
}

func (b *Builder) AddFeature(feature string) {
	b.features[feature] = true
}

func (b *Builder) AddPackage(npkg *pkg.LocalPackage) *BuildPackage {
	// Don't allow nil entries to the map
	if npkg == nil {
		panic("Cannot add nil package builder map")
	}

	bpkg := b.Packages[npkg]
	if bpkg == nil {
		bpkg = NewBuildPackage(npkg)
		b.Packages[npkg] = bpkg
	}

	return bpkg
}

// @return bool                 true if this is a new API.
func (b *Builder) AddApi(apiString string, bpkg *BuildPackage) bool {
	curBpkg := b.apis[apiString]
	if curBpkg == nil {
		b.apis[apiString] = bpkg
		return true
	} else {
		if curBpkg != bpkg {
			util.StatusMessage(util.VERBOSITY_QUIET,
				"Warning: API conflict: %s <-> %s\n", curBpkg.Name(),
				bpkg.Name())
		}
		return false
	}
}

func (b *Builder) loadDeps() error {
	// Circularly resolve dependencies, identities, APIs, and required APIs
	// until no new ones exist.
	for {
		reprocess := false
		for _, bpkg := range b.Packages {
			newDeps, newFeatures, err := bpkg.Resolve(b)
			if err != nil {
				return err
			}

			if newFeatures {
				// A new supported feature was discovered.  It is impossible to
				// determine what new dependency and API requirements are
				// generated as a result.  All packages need to be reprocessed.
				for _, bpkg := range b.Packages {
					bpkg.depsResolved = false
					bpkg.apisSatisfied = false
				}
				reprocess = true
				break
			}
			if newDeps {
				reprocess = true
			}
		}

		if !reprocess {
			break
		}
	}

	return nil
}

// Recursively compiles all the .c and .s files in the specified directory.
// Architecture-specific files are also compiled.
func buildDir(srcDir string, c *toolchain.Compiler, arch string,
	ignDirs []string) error {

	// Quietly succeed if the source directory doesn't exist.
	if util.NodeNotExist(srcDir) {
		return nil
	}

	util.StatusMessage(util.VERBOSITY_VERBOSE,
		"compiling src in base directory: %s\n", srcDir)

	// Start from the source directory.
	if err := os.Chdir(srcDir); err != nil {
		return util.NewNewtError(err.Error())
	}

	// Don't recurse into destination directories.
	ignDirs = append(ignDirs, "obj")
	ignDirs = append(ignDirs, "bin")

	// Ignore architecture-specific source files for now.  Use a temporary
	// string slice here so that the "arch" directory is not ignored in the
	// subsequent architecture-specific compile phase.
	if err := c.RecursiveCompile(toolchain.COMPILER_TYPE_C,
		append(ignDirs, "arch")); err != nil {

		return err
	}

	archDir := srcDir + "/arch/" + arch + "/"
	util.StatusMessage(util.VERBOSITY_VERBOSE,
		"compiling architecture specific src pkgs in directory: %s\n",
		archDir)

	if util.NodeExist(archDir) {
		if err := os.Chdir(archDir); err != nil {
			return util.NewNewtError(err.Error())
		}
		if err := c.RecursiveCompile(toolchain.COMPILER_TYPE_C,
			ignDirs); err != nil {

			return err
		}

		// compile assembly sources in recursive compile as well
		if err := c.RecursiveCompile(toolchain.COMPILER_TYPE_ASM,
			ignDirs); err != nil {

			return err
		}
	}

	return nil
}

func (b *Builder) newCompiler(bpkg *BuildPackage, dstDir string) (*toolchain.Compiler, error) {
	c, err := toolchain.NewCompiler(b.compilerPkg.BasePath(), dstDir,
		b.target.BuildProfile)
	if err != nil {
		return nil, err
	}
	c.AddInfo(b.compilerInfo)

	if bpkg != nil {
		ci, err := bpkg.CompilerInfo(b)
		if err != nil {
			return nil, err
		}
		c.AddInfo(ci)
	}

	// Specify all the source yml files as dependencies.  If a yml file has
	// changed, a full rebuild is required.
	for _, bp := range b.Packages {
		c.AddDeps(bp.CfgFilenames()...)
	}

	return c, nil
}

// Compiles and archives a package.
func (b *Builder) buildPackage(bpkg *BuildPackage) error {
	srcDir := bpkg.BasePath() + "/src"
	if util.NodeNotExist(srcDir) {
		// Nothing to compile.
		return nil
	}

	c, err := b.newCompiler(bpkg, b.PkgBinDir(bpkg.Name()))
	if err != nil {
		return err
	}

	// Build the package source in two phases:
	// 1. Non-test code.
	// 2. Test code (if the "test" feature is enabled).
	//
	// This is done in two passes because the structure of
	// architecture-specific directories is different for normal code and test
	// code, and not easy to generalize into a single operation:
	//     * src/arch/<target-arch>
	//     * src/test/arch/<target-arch>
	if err = buildDir(srcDir, c, b.Bsp.Arch, []string{"test"}); err != nil {
		return err
	}
	if b.features["TEST"] {
		testSrcDir := srcDir + "/test"
		if err = buildDir(testSrcDir, c, b.Bsp.Arch, nil); err != nil {
			return err
		}
	}

	// Create a static library ("archive").
	if err := os.Chdir(bpkg.BasePath() + "/"); err != nil {
		return util.NewNewtError(err.Error())
	}
	archiveFile := b.ArchivePath(bpkg.Name())
	if err = c.CompileArchive(archiveFile); err != nil {
		return err
	}

	return nil
}

func (b *Builder) link(elfName string) error {
	c, err := b.newCompiler(b.appPkg, b.PkgBinDir(elfName))
	if err != nil {
		return err
	}

	pkgNames := []string{}
	for _, bpkg := range b.Packages {
		archivePath := b.ArchivePath(bpkg.Name())
		if util.NodeExist(archivePath) {
			pkgNames = append(pkgNames, archivePath)
		}
	}

	if b.Bsp.LinkerScript != "" {
		c.LinkerScript = b.Bsp.BasePath() + b.Bsp.LinkerScript
	}
	err = c.CompileElf(elfName, pkgNames)
	if err != nil {
		return err
	}

	return nil
}

// Populates the builder with all the packages that need to be built and
// configures each package's build settings.  After this function executes,
// packages are ready to be built.
func (b *Builder) PrepBuild() error {
	if b.Bsp != nil {
		// Already prepped
		return nil
	}

	// Collect the seed packages.
	bspPkg := b.target.Bsp()
	if bspPkg == nil {
		if b.target.BspName == "" {
			return util.NewNewtError("BSP package not specified by target")
		} else {
			return util.NewNewtError("BSP package not found: " +
				b.target.BspName)
		}
	}

	b.Bsp = pkg.NewBspPackage(bspPkg)
	compilerPkg := b.resolveCompiler()
	if compilerPkg == nil {
		if b.Bsp.CompilerName == "" {
			return util.NewNewtError("Compiler package not specified by BSP")
		} else {
			return util.NewNewtError("Compiler package not found: " +
				b.Bsp.CompilerName)
		}
	}

	// An app package is not required (e.g., unit tests).
	appPkg := b.target.App()

	// Seed the builder with the app (if present), bsp, and target packages.

	var appBpkg *BuildPackage
	if appPkg != nil {
		appBpkg = b.Packages[appPkg]
		if appBpkg == nil {
			appBpkg = b.AddPackage(appPkg)
		}
		b.appPkg = appBpkg
	}

	bspBpkg := b.Packages[bspPkg]
	if bspBpkg == nil {
		bspBpkg = b.AddPackage(bspPkg)
	}

	targetBpkg := b.AddPackage(b.target.Package())

	// Populate the full set of packages to be built and resolve the feature
	// set.
	if err := b.loadDeps(); err != nil {
		return err
	}

	b.logDepInfo()

	// Terminate if any package has an unmet API requirement.
	if err := b.verifyApisSatisfied(); err != nil {
		return err
	}

	// Populate the base set of compiler flags.  Flags from the following
	// packages get applied to every source file:
	//     * app (if present)
	//     * bsp
	//     * compiler (not added here)
	//     * target

	baseCi := toolchain.NewCompilerInfo()

	// App flags.
	if appBpkg != nil {
		appCi, err := appBpkg.CompilerInfo(b)
		if err != nil {
			return err
		}
		baseCi.AddCompilerInfo(appCi)
	}

	// Bsp flags.
	bspCi, err := bspBpkg.CompilerInfo(b)
	if err != nil {
		return err
	}
	// Define a cpp symbol indicating the BSP architecture.
	bspCi.Cflags = append(bspCi.Cflags, "-DARCH_"+b.Bsp.Arch)
	baseCi.AddCompilerInfo(bspCi)

	// Target flags.
	targetCi, err := targetBpkg.CompilerInfo(b)
	if err != nil {
		return err
	}

	baseCi.AddCompilerInfo(targetCi)

	// Note: Compiler flags get added when compiler is created.

	// Read the BSP configuration.  These settings are necessary for the link
	// step.
	if err := b.Bsp.Reload(b.Features()); err != nil {
		return err
	}

	b.compilerPkg = compilerPkg
	b.compilerInfo = baseCi

	return nil
}

func (b *Builder) Build() error {
	if err := b.target.Validate(true); err != nil {
		return err
	}

	// Populate the package and feature sets and calculate the base compiler
	// flags.
	if err := b.PrepBuild(); err != nil {
		return err
	}

	// XXX: If any yml files have changed, a full rebuild is required.  We
	// don't currently check this.

	for _, bpkg := range b.Packages {
		if err := b.buildPackage(bpkg); err != nil {
			return err
		}
	}

	if err := b.link(b.AppElfPath()); err != nil {
		return err
	}

	return nil
}

func (b *Builder) Test(p *pkg.LocalPackage) error {
	if err := b.target.Validate(false); err != nil {
		return err
	}

	// Seed the builder with the package under test.
	testBpkg := b.AddPackage(p)

	// A few features are automatically supported when the test command is
	// used:
	//     * TEST:      ensures that the test code gets compiled.
	//     * SELFTEST:  indicates that there is no app.
	b.AddFeature("TEST")
	b.AddFeature("SELFTEST")

	// Populate the package and feature sets and calculate the base compiler
	// flags.
	err := b.PrepBuild()
	if err != nil {
		return err
	}

	// Define the PKG_TEST symbol while the package under test is being
	// compiled.  This symbol enables the appropriate main function that
	// usually comes from an app.
	testPkgCi, err := testBpkg.CompilerInfo(b)
	if err != nil {
		return err
	}
	testPkgCi.Cflags = append(testPkgCi.Cflags, "-DMYNEWT_SELFTEST")

	// XXX: If any yml files have changed, a full rebuild is required.  We
	// don't currently check this.

	for _, bpkg := range b.Packages {
		err = b.buildPackage(bpkg)
		if err != nil {
			return err
		}
	}

	testFilename := b.TestExePath(p.Name())
	err = b.link(testFilename)
	if err != nil {
		return err
	}

	// Run the tests.
	util.StatusMessage(util.VERBOSITY_DEFAULT, "Testing package %s\n", p.Name())

	if err := os.Chdir(filepath.Dir(testFilename)); err != nil {
		return err
	}

	o, err := util.ShellCommand(testFilename)
	if err != nil {
		util.StatusMessage(util.VERBOSITY_DEFAULT, "%s", string(o))

		return util.NewNewtError("Test crashed: " + testFilename)
	}

	util.StatusMessage(util.VERBOSITY_VERBOSE, "%s", string(o))
	util.StatusMessage(util.VERBOSITY_DEFAULT, "Test %s ok!\n", testFilename)

	return nil
}

func (b *Builder) Clean() error {
	path := b.BinDir()
	util.StatusMessage(util.VERBOSITY_VERBOSE, "Cleaning directory %s\n", path)
	err := os.RemoveAll(path)
	return err
}
<|endoftext|>


<commit_before>// +build !android

package nk

/*
#cgo CFLAGS: -DNK_INCLUDE_FIXED_TYPES -DNK_INCLUDE_STANDARD_IO -DNK_INCLUDE_DEFAULT_ALLOCATOR -DNK_INCLUDE_FONT_BAKING -DNK_INCLUDE_DEFAULT_FONT -DNK_INCLUDE_VERTEX_BUFFER_OUTPUT -Wno-implicit-function-declaration
#cgo windows LDFLAGS: -Wl,--allow-multiple-definition
#include <string.h>

#include "nuklear.h"
*/
import "C"
import (
	"unsafe"

	"github.com/veandco/go-sdl2/sdl"
)

type PlatformInitOption int

const (
	PlatformDefault PlatformInitOption = iota
	PlatformInstallCallbacks
)

func textScrollCallback(e sdl.Event, userdata interface{}) bool {
	state := userdata.(*platformState)
	switch t := e.(type) {
	case *sdl.MouseWheelEvent:
		state.scroll += float32(t.Y)
	case *sdl.KeyDownEvent:
		state.text += sdl.GetKeyName(sdl.GetKeyFromScancode(t.Keysym.Scancode))
	}
	return true
}

func NkPlatformInit(win *sdl.Window, context sdl.GLContext, opt PlatformInitOption) *Context {
	state.win = win
	if opt == PlatformInstallCallbacks {
		sdl.AddEventWatchFunc(textScrollCallback, state)
	}
	// if opt == PlatformInstallCallbacks {
	// 	win.SetScrollCallback(func(w *glfw.Window, xoff float64, yoff float64) {
	// 		state.scroll += float32(yoff)
	// 	})
	// 	win.SetCharCallback(func(w *glfw.Window, char rune) {
	// 		if len(state.text) < 256 { // NK_GLFW_TEXT_MAX
	// 			state.text += string(char)
	// 		}
	// 	})
	// }

	state.ctx = NewContext()
	NkInitDefault(state.ctx, nil)
	deviceCreate()
	return state.ctx
}

func NkPlatformShutdown() {
	NkFontAtlasClear(state.atlas)
	NkFree(state.ctx)
	deviceDestroy()
	state = nil
}

func NkFontStashBegin(atlas **FontAtlas) {
	state.atlas = NewFontAtlas()
	NkFontAtlasInitDefault(state.atlas)
	NkFontAtlasBegin(state.atlas)
	*atlas = state.atlas
}

func NkFontStashEnd() {
	var width, height int32
	image := NkFontAtlasBake(state.atlas, &width, &height, FontAtlasRgba32)
	deviceUploadAtlas(image, width, height)
	NkFontAtlasEnd(state.atlas, NkHandleId(int32(state.ogl.font_tex)), &state.ogl.null)
	if font := state.atlas.DefaultFont(); font != nil {
		NkStyleSetFont(state.ctx, font.Handle())
	}
}

func NkPlatformNewFrame() {
	win := state.win
	ctx := state.ctx
	state.width, state.height = win.GetSize()
	state.display_width, state.display_height = win.GetSize()
	state.fbScaleX = float32(state.display_width) / float32(state.width)
	state.fbScaleY = float32(state.display_height) / float32(state.height)

	NkInputBegin(ctx)
	for _, r := range state.text {
		NkInputUnicode(ctx, Rune(r))
	}

	// optional grabbing behavior
	m := ctx.Input().Mouse()
	if m.Grab() {
		sdl.SetRelativeMouseMode(true)
	} else if m.Ungrab() {
		sdl.SetRelativeMouseMode(false)
	}

	keys := sdl.GetKeyboardState()

	NkInputKey(ctx, KeyDel, int32(keys[sdl.SCANCODE_DELETE]))
	NkInputKey(ctx, KeyEnter, int32(keys[sdl.SCANCODE_RETURN]))
	NkInputKey(ctx, KeyTab, int32(keys[sdl.SCANCODE_TAB]))
	NkInputKey(ctx, KeyBackspace, int32(keys[sdl.SCANCODE_BACKSPACE]))
	NkInputKey(ctx, KeyUp, int32(keys[sdl.SCANCODE_UP]))
	NkInputKey(ctx, KeyDown, int32(keys[sdl.SCANCODE_DOWN]))
	NkInputKey(ctx, KeyTextStart, int32(keys[sdl.SCANCODE_HOME]))
	NkInputKey(ctx, KeyTextEnd, int32(keys[sdl.SCANCODE_END]))
	NkInputKey(ctx, KeyScrollStart, int32(keys[sdl.SCANCODE_HOME]))
	NkInputKey(ctx, KeyScrollEnd, int32(keys[sdl.SCANCODE_END]))
	NkInputKey(ctx, KeyScrollUp, int32(keys[sdl.SCANCODE_PAGEUP]))
	NkInputKey(ctx, KeyScrollDown, int32(keys[sdl.SCANCODE_PAGEDOWN]))

	shiftHeld := int32(0)
	if keys[sdl.KMOD_LSHIFT] == 1 || keys[sdl.KMOD_RSHIFT] == 1 {
		shiftHeld = int32(1)
	}
	NkInputKey(ctx, KeyShift, shiftHeld)

	controlHeld := false
	if keys[sdl.KMOD_LCTRL] == 1 || keys[sdl.KMOD_RCTRL] == 1 {
		controlHeld = true
	}

	if controlHeld {
		NkInputKey(ctx, KeyCopy, int32(keys[sdl.SCANCODE_C]))
		NkInputKey(ctx, KeyPaste, int32(keys[sdl.SCANCODE_V]))
		NkInputKey(ctx, KeyCut, int32(keys[sdl.SCANCODE_X]))
		NkInputKey(ctx, KeyTextUndo, int32(keys[sdl.SCANCODE_Z]))
		NkInputKey(ctx, KeyTextRedo, int32(keys[sdl.SCANCODE_R]))
		NkInputKey(ctx, KeyTextWordLeft, int32(keys[sdl.SCANCODE_LEFT]))
		NkInputKey(ctx, KeyTextWordRight, int32(keys[sdl.SCANCODE_RIGHT]))
		NkInputKey(ctx, KeyTextLineStart, int32(keys[sdl.SCANCODE_B]))
		NkInputKey(ctx, KeyTextLineEnd, int32(keys[sdl.SCANCODE_E]))
	} else {
		NkInputKey(ctx, KeyLeft, int32(keys[sdl.SCANCODE_LEFT]))
		NkInputKey(ctx, KeyRight, int32(keys[sdl.SCANCODE_RIGHT]))
		NkInputKey(ctx, KeyCopy, 0)
		NkInputKey(ctx, KeyPaste, 0)
		NkInputKey(ctx, KeyCut, 0)
		NkInputKey(ctx, KeyShift, 0)
	}

	x, y, mouseState := sdl.GetMouseState()
	NkInputMotion(ctx, int32(x), int32(y))
	// if m := ctx.Input().Mouse(); m.Grabbed() {
	// 	prevX, prevY := m.Prev()
	// 	win.SetCursorPos(float64(prevX), float64(prevY))
	// 	m.SetPos(prevX, prevY)
	// }

	NkInputButton(ctx, ButtonLeft, int32(x), int32(y), int32(mouseState&sdl.ButtonLMask()))
	NkInputButton(ctx, ButtonMiddle, int32(x), int32(y), int32(mouseState&sdl.ButtonMMask()))
	NkInputButton(ctx, ButtonRight, int32(x), int32(y), int32(mouseState&sdl.ButtonRMask()))

	NkInputScroll(ctx, state.scroll)
	NkInputEnd(ctx)
	state.text = ""
	state.scroll = 0
}

var (
	sizeofDrawIndex = unsafe.Sizeof(DrawIndex(0))
	emptyVertex     = platformVertex{}
)

type platformVertex struct {
	position [2]float32
	uv       [2]float32
	col      [4]Byte
}

const (
	platformVertexSize  = unsafe.Sizeof(platformVertex{})
	platformVertexAlign = unsafe.Alignof(platformVertex{})
)

type platformState struct {
	win *sdl.Window

	width          int
	height         int
	display_width  int
	display_height int

	ogl   *platformDevice
	ctx   *Context
	atlas *FontAtlas

	fbScaleX float32
	fbScaleY float32

	text   string
	scroll float32
}

func NkPlatformDisplayHandle() *sdl.Window {
	if state != nil {
		return state.win
	}
	return nil
}
<commit_msg>Handle mouse grab<commit_after>// +build !android

package nk

/*
#cgo CFLAGS: -DNK_INCLUDE_FIXED_TYPES -DNK_INCLUDE_STANDARD_IO -DNK_INCLUDE_DEFAULT_ALLOCATOR -DNK_INCLUDE_FONT_BAKING -DNK_INCLUDE_DEFAULT_FONT -DNK_INCLUDE_VERTEX_BUFFER_OUTPUT -Wno-implicit-function-declaration
#cgo windows LDFLAGS: -Wl,--allow-multiple-definition
#include <string.h>

#include "nuklear.h"
*/
import "C"
import (
	"unsafe"

	"github.com/veandco/go-sdl2/sdl"
)

type PlatformInitOption int

const (
	PlatformDefault PlatformInitOption = iota
	PlatformInstallCallbacks
)

func textScrollCallback(e sdl.Event, userdata interface{}) bool {
	state := userdata.(*platformState)
	switch t := e.(type) {
	case *sdl.MouseWheelEvent:
		state.scroll += float32(t.Y)
	case *sdl.KeyDownEvent:
		state.text += sdl.GetKeyName(sdl.GetKeyFromScancode(t.Keysym.Scancode))
	}
	return true
}

func NkPlatformInit(win *sdl.Window, context sdl.GLContext, opt PlatformInitOption) *Context {
	state.win = win
	if opt == PlatformInstallCallbacks {
		sdl.AddEventWatchFunc(textScrollCallback, state)
	}
	state.ctx = NewContext()
	NkInitDefault(state.ctx, nil)
	deviceCreate()
	return state.ctx
}

func NkPlatformShutdown() {
	NkFontAtlasClear(state.atlas)
	NkFree(state.ctx)
	deviceDestroy()
	state = nil
}

func NkFontStashBegin(atlas **FontAtlas) {
	state.atlas = NewFontAtlas()
	NkFontAtlasInitDefault(state.atlas)
	NkFontAtlasBegin(state.atlas)
	*atlas = state.atlas
}

func NkFontStashEnd() {
	var width, height int32
	image := NkFontAtlasBake(state.atlas, &width, &height, FontAtlasRgba32)
	deviceUploadAtlas(image, width, height)
	NkFontAtlasEnd(state.atlas, NkHandleId(int32(state.ogl.font_tex)), &state.ogl.null)
	if font := state.atlas.DefaultFont(); font != nil {
		NkStyleSetFont(state.ctx, font.Handle())
	}
}

func NkPlatformNewFrame() {
	win := state.win
	ctx := state.ctx
	state.width, state.height = win.GetSize()
	state.display_width, state.display_height = win.GetSize()
	state.fbScaleX = float32(state.display_width) / float32(state.width)
	state.fbScaleY = float32(state.display_height) / float32(state.height)

	NkInputBegin(ctx)
	for _, r := range state.text {
		NkInputUnicode(ctx, Rune(r))
	}

	// optional grabbing behavior
	m := ctx.Input().Mouse()
	if m.Grab() {
		sdl.SetRelativeMouseMode(true)
	} else if m.Ungrab() {
		sdl.SetRelativeMouseMode(false)
	}

	keys := sdl.GetKeyboardState()

	NkInputKey(ctx, KeyDel, int32(keys[sdl.SCANCODE_DELETE]))
	NkInputKey(ctx, KeyEnter, int32(keys[sdl.SCANCODE_RETURN]))
	NkInputKey(ctx, KeyTab, int32(keys[sdl.SCANCODE_TAB]))
	NkInputKey(ctx, KeyBackspace, int32(keys[sdl.SCANCODE_BACKSPACE]))
	NkInputKey(ctx, KeyUp, int32(keys[sdl.SCANCODE_UP]))
	NkInputKey(ctx, KeyDown, int32(keys[sdl.SCANCODE_DOWN]))
	NkInputKey(ctx, KeyTextStart, int32(keys[sdl.SCANCODE_HOME]))
	NkInputKey(ctx, KeyTextEnd, int32(keys[sdl.SCANCODE_END]))
	NkInputKey(ctx, KeyScrollStart, int32(keys[sdl.SCANCODE_HOME]))
	NkInputKey(ctx, KeyScrollEnd, int32(keys[sdl.SCANCODE_END]))
	NkInputKey(ctx, KeyScrollUp, int32(keys[sdl.SCANCODE_PAGEUP]))
	NkInputKey(ctx, KeyScrollDown, int32(keys[sdl.SCANCODE_PAGEDOWN]))

	shiftHeld := int32(0)
	if keys[sdl.KMOD_LSHIFT] == 1 || keys[sdl.KMOD_RSHIFT] == 1 {
		shiftHeld = int32(1)
	}
	NkInputKey(ctx, KeyShift, shiftHeld)

	controlHeld := false
	if keys[sdl.KMOD_LCTRL] == 1 || keys[sdl.KMOD_RCTRL] == 1 {
		controlHeld = true
	}

	if controlHeld {
		NkInputKey(ctx, KeyCopy, int32(keys[sdl.SCANCODE_C]))
		NkInputKey(ctx, KeyPaste, int32(keys[sdl.SCANCODE_V]))
		NkInputKey(ctx, KeyCut, int32(keys[sdl.SCANCODE_X]))
		NkInputKey(ctx, KeyTextUndo, int32(keys[sdl.SCANCODE_Z]))
		NkInputKey(ctx, KeyTextRedo, int32(keys[sdl.SCANCODE_R]))
		NkInputKey(ctx, KeyTextWordLeft, int32(keys[sdl.SCANCODE_LEFT]))
		NkInputKey(ctx, KeyTextWordRight, int32(keys[sdl.SCANCODE_RIGHT]))
		NkInputKey(ctx, KeyTextLineStart, int32(keys[sdl.SCANCODE_B]))
		NkInputKey(ctx, KeyTextLineEnd, int32(keys[sdl.SCANCODE_E]))
	} else {
		NkInputKey(ctx, KeyLeft, int32(keys[sdl.SCANCODE_LEFT]))
		NkInputKey(ctx, KeyRight, int32(keys[sdl.SCANCODE_RIGHT]))
		NkInputKey(ctx, KeyCopy, 0)
		NkInputKey(ctx, KeyPaste, 0)
		NkInputKey(ctx, KeyCut, 0)
		NkInputKey(ctx, KeyShift, 0)
	}

	x, y, mouseState := sdl.GetMouseState()
	NkInputMotion(ctx, int32(x), int32(y))
	if m := ctx.Input().Mouse(); m.Grabbed() {
		prevX, prevY := m.Prev()
		win.WarpMouseInWindow(int(prevX), int(prevY))
		m.SetPos(prevX, prevY)
	}

	NkInputButton(ctx, ButtonLeft, int32(x), int32(y), int32(mouseState&sdl.ButtonLMask()))
	NkInputButton(ctx, ButtonMiddle, int32(x), int32(y), int32(mouseState&sdl.ButtonMMask()))
	NkInputButton(ctx, ButtonRight, int32(x), int32(y), int32(mouseState&sdl.ButtonRMask()))

	NkInputScroll(ctx, state.scroll)
	NkInputEnd(ctx)
	state.text = ""
	state.scroll = 0
}

var (
	sizeofDrawIndex = unsafe.Sizeof(DrawIndex(0))
	emptyVertex     = platformVertex{}
)

type platformVertex struct {
	position [2]float32
	uv       [2]float32
	col      [4]Byte
}

const (
	platformVertexSize  = unsafe.Sizeof(platformVertex{})
	platformVertexAlign = unsafe.Alignof(platformVertex{})
)

type platformState struct {
	win *sdl.Window

	width          int
	height         int
	display_width  int
	display_height int

	ogl   *platformDevice
	ctx   *Context
	atlas *FontAtlas

	fbScaleX float32
	fbScaleY float32

	text   string
	scroll float32
}

func NkPlatformDisplayHandle() *sdl.Window {
	if state != nil {
		return state.win
	}
	return nil
}
<|endoftext|>


<commit_before>package state

import (
	"fmt"

	"github.com/hashicorp/go-memdb"
	"github.com/hashicorp/nomad/nomad/structs"
)

// stateStoreSchema is used to return the schema for the state store
func stateStoreSchema() *memdb.DBSchema {
	// Create the root DB schema
	db := &memdb.DBSchema{
		Tables: make(map[string]*memdb.TableSchema),
	}

	// Collect all the schemas that are needed
	schemas := []func() *memdb.TableSchema{
		indexTableSchema,
		nodeTableSchema,
		jobTableSchema,
		periodicLaunchTableSchema,
		evalTableSchema,
		allocTableSchema,
	}

	// Add each of the tables
	for _, schemaFn := range schemas {
		schema := schemaFn()
		if _, ok := db.Tables[schema.Name]; ok {
			panic(fmt.Sprintf("duplicate table name: %s", schema.Name))
		}
		db.Tables[schema.Name] = schema
	}
	return db
}

// indexTableSchema is used for
func indexTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "index",
		Indexes: map[string]*memdb.IndexSchema{
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.StringFieldIndex{
					Field:     "Key",
					Lowercase: true,
				},
			},
		},
	}
}

// nodeTableSchema returns the MemDB schema for the nodes table.
// This table is used to store all the client nodes that are registered.
func nodeTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "nodes",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is used for node management
			// and simple direct lookup. ID is required to be
			// unique.
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.UUIDFieldIndex{
					Field: "ID",
				},
			},
		},
	}
}

// jobTableSchema returns the MemDB schema for the jobs table.
// This table is used to store all the jobs that have been submitted.
func jobTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "jobs",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is used for job management
			// and simple direct lookup. ID is required to be
			// unique.
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.StringFieldIndex{
					Field:     "ID",
					Lowercase: true,
				},
			},
			"type": &memdb.IndexSchema{
				Name:         "type",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.StringFieldIndex{
					Field:     "Type",
					Lowercase: false,
				},
			},
			"gc": &memdb.IndexSchema{
				Name:         "gc",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.ConditionalIndex{
					Conditional: jobIsGCable,
				},
			},
			"periodic": &memdb.IndexSchema{
				Name:         "periodic",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.ConditionalIndex{
					Conditional: jobIsPeriodic,
				},
			},
		},
	}
}

// jobIsGCable satisfies the ConditionalIndexFunc interface and creates an index
// on whether a job is eligible for garbage collection.
func jobIsGCable(obj interface{}) (bool, error) {
	j, ok := obj.(*structs.Job)
	if !ok {
		return false, fmt.Errorf("Unexpected type: %v", obj)
	}

	// The job is GCable if it is batch and it is not periodic
	periodic := j.Periodic != nil && j.Periodic.Enabled
	gcable := j.Type == structs.JobTypeBatch && !periodic
	return gcable, nil
}

// jobIsPeriodic satisfies the ConditionalIndexFunc interface and creates an index
// on whether a job is periodic.
func jobIsPeriodic(obj interface{}) (bool, error) {
	j, ok := obj.(*structs.Job)
	if !ok {
		return false, fmt.Errorf("Unexpected type: %v", obj)
	}

	if j.Periodic != nil && j.Periodic.Enabled == true {
		return true, nil
	}

	return false, nil
}

// periodicLaunchTableSchema returns the MemDB schema tracking the most recent
// launch time for a perioidic job.
func periodicLaunchTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "periodic_launch",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is used for job management
			// and simple direct lookup. ID is required to be
			// unique.
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.StringFieldIndex{
					Field:     "ID",
					Lowercase: true,
				},
			},
		},
	}
}

// evalTableSchema returns the MemDB schema for the eval table.
// This table is used to store all the evaluations that are pending
// or recently completed.
func evalTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "evals",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is used for direct lookup.
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.UUIDFieldIndex{
					Field: "ID",
				},
			},

			// Job index is used to lookup allocations by job
			"job": &memdb.IndexSchema{
				Name:         "job",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.StringFieldIndex{
					Field:     "JobID",
					Lowercase: true,
				},
			},
		},
	}
}

// allocTableSchema returns the MemDB schema for the allocation table.
// This table is used to store all the task allocations between task groups
// and nodes.
func allocTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "allocs",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is a UUID
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.UUIDFieldIndex{
					Field: "ID",
				},
			},

			// Node index is used to lookup allocations by node
			"node": &memdb.IndexSchema{
				Name:         "node",
				AllowMissing: true, // Missing is allow for failed allocations
				Unique:       false,
				Indexer: &memdb.CompoundIndex{
					Indexes: []memdb.Indexer{
						&memdb.StringFieldIndex{
							Field:     "NodeID",
							Lowercase: true,
						},

						// Conditional indexer on if allocation is terminal
						&memdb.ConditionalIndex{
							Conditional: func(obj interface{}) (bool, error) {
								// Cast to allocation
								alloc, ok := obj.(*structs.Allocation)
								if !ok {
									return false, fmt.Errorf("wrong type, got %t should be Allocation", obj)
								}

								// Check if the allocation is terminal
								return alloc.TerminalStatus(), nil
							},
						},
					},
				},
			},

			// Job index is used to lookup allocations by job
			"job": &memdb.IndexSchema{
				Name:         "job",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.StringFieldIndex{
					Field:     "JobID",
					Lowercase: true,
				},
			},

			// Eval index is used to lookup allocations by eval
			"eval": &memdb.IndexSchema{
				Name:         "eval",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.UUIDFieldIndex{
					Field: "EvalID",
				},
			},
		},
	}
}
<commit_msg>Added a schema for summarizing status of jobs<commit_after>package state

import (
	"fmt"

	"github.com/hashicorp/go-memdb"
	"github.com/hashicorp/nomad/nomad/structs"
)

// stateStoreSchema is used to return the schema for the state store
func stateStoreSchema() *memdb.DBSchema {
	// Create the root DB schema
	db := &memdb.DBSchema{
		Tables: make(map[string]*memdb.TableSchema),
	}

	// Collect all the schemas that are needed
	schemas := []func() *memdb.TableSchema{
		indexTableSchema,
		nodeTableSchema,
		jobTableSchema,
		jobSummarySchema,
		periodicLaunchTableSchema,
		evalTableSchema,
		allocTableSchema,
	}

	// Add each of the tables
	for _, schemaFn := range schemas {
		schema := schemaFn()
		if _, ok := db.Tables[schema.Name]; ok {
			panic(fmt.Sprintf("duplicate table name: %s", schema.Name))
		}
		db.Tables[schema.Name] = schema
	}
	return db
}

// indexTableSchema is used for
func indexTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "index",
		Indexes: map[string]*memdb.IndexSchema{
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.StringFieldIndex{
					Field:     "Key",
					Lowercase: true,
				},
			},
		},
	}
}

// nodeTableSchema returns the MemDB schema for the nodes table.
// This table is used to store all the client nodes that are registered.
func nodeTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "nodes",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is used for node management
			// and simple direct lookup. ID is required to be
			// unique.
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.UUIDFieldIndex{
					Field: "ID",
				},
			},
		},
	}
}

// jobTableSchema returns the MemDB schema for the jobs table.
// This table is used to store all the jobs that have been submitted.
func jobTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "jobs",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is used for job management
			// and simple direct lookup. ID is required to be
			// unique.
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.StringFieldIndex{
					Field:     "ID",
					Lowercase: true,
				},
			},
			"type": &memdb.IndexSchema{
				Name:         "type",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.StringFieldIndex{
					Field:     "Type",
					Lowercase: false,
				},
			},
			"gc": &memdb.IndexSchema{
				Name:         "gc",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.ConditionalIndex{
					Conditional: jobIsGCable,
				},
			},
			"periodic": &memdb.IndexSchema{
				Name:         "periodic",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.ConditionalIndex{
					Conditional: jobIsPeriodic,
				},
			},
		},
	}
}

// jobSummarySchema returns the memdb schema for the job summary table
func jobSummarySchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "jobsummary",
		Indexes: map[string]*memdb.IndexSchema{
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.StringFieldIndex{
					Field:     "ID",
					Lowercase: true,
				},
			},
		},
	}
}

// jobIsGCable satisfies the ConditionalIndexFunc interface and creates an index
// on whether a job is eligible for garbage collection.
func jobIsGCable(obj interface{}) (bool, error) {
	j, ok := obj.(*structs.Job)
	if !ok {
		return false, fmt.Errorf("Unexpected type: %v", obj)
	}

	// The job is GCable if it is batch and it is not periodic
	periodic := j.Periodic != nil && j.Periodic.Enabled
	gcable := j.Type == structs.JobTypeBatch && !periodic
	return gcable, nil
}

// jobIsPeriodic satisfies the ConditionalIndexFunc interface and creates an index
// on whether a job is periodic.
func jobIsPeriodic(obj interface{}) (bool, error) {
	j, ok := obj.(*structs.Job)
	if !ok {
		return false, fmt.Errorf("Unexpected type: %v", obj)
	}

	if j.Periodic != nil && j.Periodic.Enabled == true {
		return true, nil
	}

	return false, nil
}

// periodicLaunchTableSchema returns the MemDB schema tracking the most recent
// launch time for a perioidic job.
func periodicLaunchTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "periodic_launch",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is used for job management
			// and simple direct lookup. ID is required to be
			// unique.
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.StringFieldIndex{
					Field:     "ID",
					Lowercase: true,
				},
			},
		},
	}
}

// evalTableSchema returns the MemDB schema for the eval table.
// This table is used to store all the evaluations that are pending
// or recently completed.
func evalTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "evals",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is used for direct lookup.
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.UUIDFieldIndex{
					Field: "ID",
				},
			},

			// Job index is used to lookup allocations by job
			"job": &memdb.IndexSchema{
				Name:         "job",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.StringFieldIndex{
					Field:     "JobID",
					Lowercase: true,
				},
			},
		},
	}
}

// allocTableSchema returns the MemDB schema for the allocation table.
// This table is used to store all the task allocations between task groups
// and nodes.
func allocTableSchema() *memdb.TableSchema {
	return &memdb.TableSchema{
		Name: "allocs",
		Indexes: map[string]*memdb.IndexSchema{
			// Primary index is a UUID
			"id": &memdb.IndexSchema{
				Name:         "id",
				AllowMissing: false,
				Unique:       true,
				Indexer: &memdb.UUIDFieldIndex{
					Field: "ID",
				},
			},

			// Node index is used to lookup allocations by node
			"node": &memdb.IndexSchema{
				Name:         "node",
				AllowMissing: true, // Missing is allow for failed allocations
				Unique:       false,
				Indexer: &memdb.CompoundIndex{
					Indexes: []memdb.Indexer{
						&memdb.StringFieldIndex{
							Field:     "NodeID",
							Lowercase: true,
						},

						// Conditional indexer on if allocation is terminal
						&memdb.ConditionalIndex{
							Conditional: func(obj interface{}) (bool, error) {
								// Cast to allocation
								alloc, ok := obj.(*structs.Allocation)
								if !ok {
									return false, fmt.Errorf("wrong type, got %t should be Allocation", obj)
								}

								// Check if the allocation is terminal
								return alloc.TerminalStatus(), nil
							},
						},
					},
				},
			},

			// Job index is used to lookup allocations by job
			"job": &memdb.IndexSchema{
				Name:         "job",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.StringFieldIndex{
					Field:     "JobID",
					Lowercase: true,
				},
			},

			// Eval index is used to lookup allocations by eval
			"eval": &memdb.IndexSchema{
				Name:         "eval",
				AllowMissing: false,
				Unique:       false,
				Indexer: &memdb.UUIDFieldIndex{
					Field: "EvalID",
				},
			},
		},
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package filter

import (
	"io"

	"github.com/syndtr/goleveldb/leveldb/hash"
)

func bloomHash(key []byte) uint32 {
	return hash.Hash(key, 0xbc9f1d34)
}

// BloomFilter filter represent a bloom filter.
type BloomFilter struct {
	bitsPerKey, k uint32
}

// NewBloomFilter create new initialized bloom filter for given
// bitsPerKey.
func NewBloomFilter(bitsPerKey int) *BloomFilter {
	// We intentionally round down to reduce probing cost a little bit
	k := uint32(bitsPerKey) * 69 / 100 // 0.69 =~ ln(2)
	if k < 1 {
		k = 1
	} else if k > 30 {
		k = 30
	}
	return &BloomFilter{uint32(bitsPerKey), k}
}

// Name return the name of this filter. i.e. "leveldb.BuiltinBloomFilter".
func (*BloomFilter) Name() string {
	return "leveldb.BuiltinBloomFilter"
}

// CreateFilter generate filter for given set of keys and write it to
// given buffer.
func (p *BloomFilter) CreateFilter(keys [][]byte, buf io.Writer) {
	// Compute bloom filter size (in both bits and bytes)
	bits := uint32(len(keys)) * p.bitsPerKey

	// For small n, we can see a very high false positive rate.  Fix it
	// by enforcing a minimum bloom filter length.
	if bits < 64 {
		bits = 64
	}

	bytes := (bits + 7) / 8
	bits = bytes * 8

	array := make([]byte, bytes)

	for _, key := range keys {
		// Use double-hashing to generate a sequence of hash values.
		// See analysis in [Kirsch,Mitzenmacher 2006].
		h := bloomHash(key)
		delta := (h >> 17) | (h << 15) // Rotate right 17 bits
		for i := uint32(0); i < p.k; i++ {
			bitpos := h % bits
			array[bitpos/8] |= (1 << (bitpos % 8))
			h += delta
		}
	}

	buf.Write(array)
	buf.Write([]byte{byte(p.k)})
}

// KeyMayMatch test whether given key on the list.
func (p *BloomFilter) KeyMayMatch(key, filter []byte) bool {
	l := uint32(len(filter))
	if l < 2 {
		return false
	}

	bits := (l - 1) * 8

	// Use the encoded k so that we can read filters generated by
	// bloom filters created using different parameters.
	k := uint32(filter[l-1])
	if k > 30 {
		// Reserved for potentially new encodings for short bloom filters.
		// Consider it a match.
		return true
	}

	h := bloomHash(key)
	delta := (h >> 17) | (h << 15) // Rotate right 17 bits
	for i := uint32(0); i < k; i++ {
		bitpos := h % bits
		if (uint32(filter[bitpos/8]) & (1 << (bitpos % 8))) == 0 {
			return false
		}
		h += delta
	}

	return true
}
<commit_msg>Documenting backward compatability of bloom filter<commit_after>// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package filter

import (
	"io"

	"github.com/syndtr/goleveldb/leveldb/hash"
)

func bloomHash(key []byte) uint32 {
	return hash.Hash(key, 0xbc9f1d34)
}

// BloomFilter filter represent a bloom filter.
type BloomFilter struct {
	bitsPerKey, k uint32
}

// NewBloomFilter create new initialized bloom filter for given
// bitsPerKey.
//
// Since bitsPerKey is persisted individually for each bloom filter
// serialization, bloom filters are backwards compatible with respect to
// changing bitsPerKey. This means that no big performance penalty will
// be experienced when changing the parameter. See documentation for
// opt.Options.Filter for more information.
func NewBloomFilter(bitsPerKey int) *BloomFilter {
	// We intentionally round down to reduce probing cost a little bit
	k := uint32(bitsPerKey) * 69 / 100 // 0.69 =~ ln(2)
	if k < 1 {
		k = 1
	} else if k > 30 {
		k = 30
	}
	return &BloomFilter{uint32(bitsPerKey), k}
}

// Name return the name of this filter. i.e. "leveldb.BuiltinBloomFilter".
//
// The bloom filter serializes its parameters and is backward compatible
// with respect to them. Therefor, its parameters are not added to its
// name.
func (*BloomFilter) Name() string {
	return "leveldb.BuiltinBloomFilter"
}

// CreateFilter generate filter for given set of keys and write it to
// given buffer.
func (p *BloomFilter) CreateFilter(keys [][]byte, buf io.Writer) {
	// Compute bloom filter size (in both bits and bytes)
	bits := uint32(len(keys)) * p.bitsPerKey

	// For small n, we can see a very high false positive rate.  Fix it
	// by enforcing a minimum bloom filter length.
	if bits < 64 {
		bits = 64
	}

	bytes := (bits + 7) / 8
	bits = bytes * 8

	array := make([]byte, bytes)

	for _, key := range keys {
		// Use double-hashing to generate a sequence of hash values.
		// See analysis in [Kirsch,Mitzenmacher 2006].
		h := bloomHash(key)
		delta := (h >> 17) | (h << 15) // Rotate right 17 bits
		for i := uint32(0); i < p.k; i++ {
			bitpos := h % bits
			array[bitpos/8] |= (1 << (bitpos % 8))
			h += delta
		}
	}

	buf.Write(array)
	buf.Write([]byte{byte(p.k)})
}

// KeyMayMatch test whether given key on the list.
func (p *BloomFilter) KeyMayMatch(key, filter []byte) bool {
	l := uint32(len(filter))
	if l < 2 {
		return false
	}

	bits := (l - 1) * 8

	// Use the encoded k so that we can read filters generated by
	// bloom filters created using different parameters.
	k := uint32(filter[l-1])
	if k > 30 {
		// Reserved for potentially new encodings for short bloom filters.
		// Consider it a match.
		return true
	}

	h := bloomHash(key)
	delta := (h >> 17) | (h << 15) // Rotate right 17 bits
	for i := uint32(0); i < k; i++ {
		bitpos := h % bits
		if (uint32(filter[bitpos/8]) & (1 << (bitpos % 8))) == 0 {
			return false
		}
		h += delta
	}

	return true
}
<|endoftext|>


<commit_before>package modifyset

import (
	"encoding/json"
	"fmt"
	"strings"
	"testing"

	"github.com/open-policy-agent/gatekeeper/apis/mutations/unversioned"
	"github.com/open-policy-agent/gatekeeper/pkg/mutation/match"
	"github.com/open-policy-agent/gatekeeper/pkg/mutation/path/tester"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
)

func makeValue(v interface{}) runtime.RawExtension {
	v2 := map[string]interface{}{
		"value": v,
	}
	j, err := json.Marshal(v2)
	if err != nil {
		panic(err)
	}
	return runtime.RawExtension{Raw: j}
}

func modifyset(value interface{}, location string) *unversioned.ModifySet {
	result := &unversioned.ModifySet{
		Spec: unversioned.ModifySetSpec{
			ApplyTo: []match.ApplyTo{{
				Groups:   []string{"*"},
				Versions: []string{"*"},
				Kinds:    []string{"*"},
			}},
			Location: location,
			Parameters: unversioned.ModifySetParameters{
				Values: unversioned.Values{
					FromList: []interface{}{makeValue(value)},
				},
			},
		},
	}

	return result
}

func benchmarkModifySetMutator(b *testing.B, n int) {
	mutator, err := MutatorForModifySet(modifyset("foo", "spec"+strings.Repeat(".spec", n-1)))
	if err != nil {
		b.Fatal(err)
	}

	obj := &unstructured.Unstructured{
		Object: make(map[string]interface{}),
	}
	p := make([]string, n)
	for i := 0; i < n; i++ {
		p[i] = "spec"
	}
	_, err = mutator.Mutate(obj)
	if err != nil {
		b.Fatal(err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = mutator.Mutate(obj)
	}
}

func benchmarkNoModifySetMutator(b *testing.B, n int) {
	path := "spec" + strings.Repeat(".spec", n-1)
	a := modifyset("foo", path)
	a.Spec.Parameters.PathTests = []unversioned.PathTest{{
		SubPath:   path,
		Condition: tester.MustNotExist,
	}}
	mutator, err := MutatorForModifySet(a)
	if err != nil {
		b.Fatal(err)
	}

	obj := &unstructured.Unstructured{
		Object: make(map[string]interface{}),
	}
	p := make([]string, n)
	for i := 0; i < n; i++ {
		p[i] = "spec"
	}
	_, err = mutator.Mutate(obj)
	if err != nil {
		b.Fatal(err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = mutator.Mutate(obj)
	}
}

func BenchmarkModifySetMutator_Mutate(b *testing.B) {
	ns := []int{1, 2, 5, 10, 20}

	for _, n := range ns {
		b.Run(fmt.Sprintf("always mutate %d-depth", n), func(b *testing.B) {
			benchmarkModifySetMutator(b, n)
		})
	}

	for _, n := range ns {
		b.Run(fmt.Sprintf("never mutate %d-depth", n), func(b *testing.B) {
			benchmarkNoModifySetMutator(b, n)
		})
	}
}
<commit_msg>test: Fix `BenchmarkModifySetMutator_Mutate` was broken (#1897)<commit_after>package modifyset

import (
	"fmt"
	"strings"
	"testing"

	"github.com/open-policy-agent/gatekeeper/apis/mutations/unversioned"
	"github.com/open-policy-agent/gatekeeper/pkg/mutation/match"
	"github.com/open-policy-agent/gatekeeper/pkg/mutation/path/tester"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func modifyset(value interface{}, location string) *unversioned.ModifySet {
	result := &unversioned.ModifySet{
		Spec: unversioned.ModifySetSpec{
			ApplyTo: []match.ApplyTo{{
				Groups:   []string{"*"},
				Versions: []string{"*"},
				Kinds:    []string{"*"},
			}},
			Location: location,
			Parameters: unversioned.ModifySetParameters{
				Operation: unversioned.MergeOp,
				Values: unversioned.Values{
					FromList: []interface{}{value},
				},
			},
		},
	}

	return result
}

func benchmarkModifySetMutator(b *testing.B, n int) {
	mutator, err := MutatorForModifySet(modifyset("foo", "spec"+strings.Repeat(".spec", n-1)))
	if err != nil {
		b.Fatal(err)
	}

	obj := &unstructured.Unstructured{
		Object: make(map[string]interface{}),
	}
	p := make([]string, n)
	for i := 0; i < n; i++ {
		p[i] = "spec"
	}
	_, err = mutator.Mutate(obj)
	if err != nil {
		b.Fatal(err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = mutator.Mutate(obj)
	}
}

func benchmarkNoModifySetMutator(b *testing.B, n int) {
	path := "spec" + strings.Repeat(".spec", n-1)
	a := modifyset("foo", path)
	a.Spec.Parameters.PathTests = []unversioned.PathTest{{
		SubPath:   path,
		Condition: tester.MustNotExist,
	}}
	mutator, err := MutatorForModifySet(a)
	if err != nil {
		b.Fatal(err)
	}

	obj := &unstructured.Unstructured{
		Object: make(map[string]interface{}),
	}
	p := make([]string, n)
	for i := 0; i < n; i++ {
		p[i] = "spec"
	}
	_, err = mutator.Mutate(obj)
	if err != nil {
		b.Fatal(err)
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = mutator.Mutate(obj)
	}
}

func BenchmarkModifySetMutator_Mutate(b *testing.B) {
	ns := []int{1, 2, 5, 10, 20}

	for _, n := range ns {
		b.Run(fmt.Sprintf("always mutate %d-depth", n), func(b *testing.B) {
			benchmarkModifySetMutator(b, n)
		})
	}

	for _, n := range ns {
		b.Run(fmt.Sprintf("never mutate %d-depth", n), func(b *testing.B) {
			benchmarkNoModifySetMutator(b, n)
		})
	}
}
<|endoftext|>