<commit_before>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cache

import (
	"bytes"
	"context"
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"path"
	"sort"
	"strings"
	"sync"

	"golang.org/x/tools/go/packages"
	"golang.org/x/tools/internal/lsp/source"
	"golang.org/x/tools/internal/lsp/telemetry"
	"golang.org/x/tools/internal/memoize"
	"golang.org/x/tools/internal/span"
	"golang.org/x/tools/internal/telemetry/log"
	"golang.org/x/tools/internal/telemetry/trace"
	errors "golang.org/x/xerrors"
)

type packageHandleKey string

// packageHandle implements source.PackageHandle.
type packageHandle struct {
	handle *memoize.Handle

	goFiles []source.ParseGoHandle

	// compiledGoFiles are the ParseGoHandles that compose the package.
	compiledGoFiles []source.ParseGoHandle

	// mode is the mode the the files were parsed in.
	mode source.ParseMode

	// m is the metadata associated with the package.
	m *metadata

	// key is the hashed key for the package.
	key packageHandleKey
}

func (ph *packageHandle) packageKey() packageKey {
	return packageKey{
		id:   ph.m.id,
		mode: ph.mode,
	}
}

// packageData contains the data produced by type-checking a package.
type packageData struct {
	memoize.NoCopy

	pkg *pkg
	err error
}

// buildPackageHandle returns a source.PackageHandle for a given package and config.
func (s *snapshot) buildPackageHandle(ctx context.Context, id packageID) (*packageHandle, error) {
	if ph := s.getPackage(id); ph != nil {
		return ph, nil
	}

	// Build the PackageHandle for this ID and its dependencies.
	ph, deps, err := s.buildKey(ctx, id)
	if err != nil {
		return nil, err
	}

	// Do not close over the packageHandle or the snapshot in the Bind function.
	// This creates a cycle, which causes the finalizers to never run on the handles.
	// The possible cycles are:
	//
	//     packageHandle.h.function -> packageHandle
	//     packageHandle.h.function -> snapshot -> packageHandle
	//

	m := ph.m
	mode := ph.mode
	goFiles := ph.goFiles
	compiledGoFiles := ph.compiledGoFiles
	key := ph.key
	fset := s.view.session.cache.fset

	h := s.view.session.cache.store.Bind(key, func(ctx context.Context) interface{} {
		// Begin loading the direct dependencies, in parallel.
		for _, dep := range deps {
			go func(dep *packageHandle) {
				dep.check(ctx)
			}(dep)
		}
		data := &packageData{}
		data.pkg, data.err = typeCheck(ctx, fset, m, mode, goFiles, compiledGoFiles, deps)
		return data
	})
	ph.handle = h

	// Cache the PackageHandle in the snapshot.
	s.addPackage(ph)

	return ph, nil
}

// buildKey computes the key for a given packageHandle.
func (s *snapshot) buildKey(ctx context.Context, id packageID) (*packageHandle, map[packagePath]*packageHandle, error) {
	m := s.getMetadata(id)
	if m == nil {
		return nil, nil, errors.Errorf("no metadata for %s", id)
	}
	mode := s.packageMode(id)
	goFiles, err := s.parseGoHandles(ctx, m.goFiles, mode)
	if err != nil {
		return nil, nil, err
	}
	compiledGoFiles, err := s.parseGoHandles(ctx, m.compiledGoFiles, mode)
	if err != nil {
		return nil, nil, err
	}
	ph := &packageHandle{
		m:               m,
		goFiles:         goFiles,
		compiledGoFiles: compiledGoFiles,
		mode:            mode,
	}
	// Make sure all of the depList are sorted.
	depList := append([]packageID{}, m.deps...)
	sort.Slice(depList, func(i, j int) bool {
		return depList[i] < depList[j]
	})

	deps := make(map[packagePath]*packageHandle)

	// Begin computing the key by getting the depKeys for all dependencies.
	var depKeys []packageHandleKey
	for _, depID := range depList {
		depHandle, err := s.buildPackageHandle(ctx, depID)
		if err != nil {
			log.Error(ctx, "no dep handle", err, telemetry.Package.Of(depID))

			// One bad dependency should not prevent us from checking the entire package.
			// Add a special key to mark a bad dependency.
			depKeys = append(depKeys, packageHandleKey(fmt.Sprintf("%s import not found", id)))
			continue
		}
		deps[depHandle.m.pkgPath] = depHandle
		depKeys = append(depKeys, depHandle.key)
	}
	ph.key = checkPackageKey(ph.m.id, ph.compiledGoFiles, m.config, depKeys)
	return ph, deps, nil
}

func checkPackageKey(id packageID, pghs []source.ParseGoHandle, cfg *packages.Config, deps []packageHandleKey) packageHandleKey {
	var depBytes []byte
	for _, dep := range deps {
		depBytes = append(depBytes, []byte(dep)...)
	}
	return packageHandleKey(hashContents([]byte(fmt.Sprintf("%s%s%s%s", id, hashParseKeys(pghs), hashConfig(cfg), hashContents(depBytes)))))
}

// hashConfig returns the hash for the *packages.Config.
func hashConfig(config *packages.Config) string {
	b := bytes.NewBuffer(nil)

	// Dir, Mode, Env, BuildFlags are the parts of the config that can change.
	b.WriteString(config.Dir)
	b.WriteString(string(config.Mode))

	for _, e := range config.Env {
		b.WriteString(e)
	}
	for _, f := range config.BuildFlags {
		b.WriteString(f)
	}
	return hashContents(b.Bytes())
}

func (ph *packageHandle) Check(ctx context.Context) (source.Package, error) {
	return ph.check(ctx)
}

func (ph *packageHandle) check(ctx context.Context) (*pkg, error) {
	v := ph.handle.Get(ctx)
	if v == nil {
		return nil, ctx.Err()
	}
	data := v.(*packageData)
	return data.pkg, data.err
}

func (ph *packageHandle) CompiledGoFiles() []source.ParseGoHandle {
	return ph.compiledGoFiles
}

func (ph *packageHandle) ID() string {
	return string(ph.m.id)
}

func (ph *packageHandle) MissingDependencies() []string {
	var md []string
	for i := range ph.m.missingDeps {
		md = append(md, string(i))
	}
	return md
}

func hashImports(ctx context.Context, wsPackages []source.PackageHandle) (string, error) {
	results := make(map[string]bool)
	var imports []string
	for _, ph := range wsPackages {
		// Check package since we do not always invalidate the metadata.
		pkg, err := ph.Check(ctx)
		if err != nil {
			return "", err
		}
		for _, path := range pkg.Imports() {
			imp := path.PkgPath()
			if _, ok := results[imp]; !ok {
				results[imp] = true
				imports = append(imports, imp)
			}
		}
	}
	sort.Strings(imports)
	hashed := strings.Join(imports, ",")
	return hashContents([]byte(hashed)), nil
}

func (ph *packageHandle) Cached() (source.Package, error) {
	return ph.cached()
}

func (ph *packageHandle) cached() (*pkg, error) {
	v := ph.handle.Cached()
	if v == nil {
		return nil, errors.Errorf("no cached type information for %s", ph.m.pkgPath)
	}
	data := v.(*packageData)
	return data.pkg, data.err
}

func (s *snapshot) parseGoHandles(ctx context.Context, files []span.URI, mode source.ParseMode) ([]source.ParseGoHandle, error) {
	phs := make([]source.ParseGoHandle, 0, len(files))
	for _, uri := range files {
		fh, err := s.GetFile(uri)
		if err != nil {
			return nil, err
		}
		phs = append(phs, s.view.session.cache.ParseGoHandle(fh, mode))
	}
	return phs, nil
}

func typeCheck(ctx context.Context, fset *token.FileSet, m *metadata, mode source.ParseMode, goFiles []source.ParseGoHandle, compiledGoFiles []source.ParseGoHandle, deps map[packagePath]*packageHandle) (*pkg, error) {
	ctx, done := trace.StartSpan(ctx, "cache.importer.typeCheck", telemetry.Package.Of(m.id))
	defer done()

	var rawErrors []error
	for _, err := range m.errors {
		rawErrors = append(rawErrors, err)
	}

	pkg := &pkg{
		id:              m.id,
		pkgPath:         m.pkgPath,
		mode:            mode,
		goFiles:         goFiles,
		compiledGoFiles: compiledGoFiles,
		imports:         make(map[packagePath]*pkg),
		typesSizes:      m.typesSizes,
		typesInfo: &types.Info{
			Types:      make(map[ast.Expr]types.TypeAndValue),
			Defs:       make(map[*ast.Ident]types.Object),
			Uses:       make(map[*ast.Ident]types.Object),
			Implicits:  make(map[ast.Node]types.Object),
			Selections: make(map[*ast.SelectorExpr]*types.Selection),
			Scopes:     make(map[ast.Node]*types.Scope),
		},
		forTest: m.forTest,
	}
	var (
		files        = make([]*ast.File, len(pkg.compiledGoFiles))
		parseErrors  = make([]error, len(pkg.compiledGoFiles))
		actualErrors = make([]error, len(pkg.compiledGoFiles))
		wg           sync.WaitGroup
	)
	for i, ph := range pkg.compiledGoFiles {
		wg.Add(1)
		go func(i int, ph source.ParseGoHandle) {
			files[i], _, parseErrors[i], actualErrors[i] = ph.Parse(ctx)
			wg.Done()
		}(i, ph)
	}
	for _, ph := range pkg.goFiles {
		wg.Add(1)
		// We need to parse the non-compiled go files, but we don't care about their errors.
		go func(ph source.ParseGoHandle) {
			ph.Parse(ctx)
			wg.Done()
		}(ph)
	}
	wg.Wait()

	for _, e := range parseErrors {
		if e != nil {
			rawErrors = append(rawErrors, e)
		}
	}

	var i int
	for _, f := range files {
		if f != nil {
			files[i] = f
			i++
		}
	}
	files = files[:i]

	// Use the default type information for the unsafe package.
	if pkg.pkgPath == "unsafe" {
		pkg.types = types.Unsafe
	} else if len(files) == 0 { // not the unsafe package, no parsed files
		return nil, errors.Errorf("no parsed files for package %s, expected: %s, errors: %v, list errors: %v", pkg.pkgPath, pkg.compiledGoFiles, actualErrors, rawErrors)
	} else {
		pkg.types = types.NewPackage(string(m.pkgPath), m.name)
	}

	cfg := &types.Config{
		Error: func(e error) {
			rawErrors = append(rawErrors, e)
		},
		Importer: importerFunc(func(pkgPath string) (*types.Package, error) {
			// If the context was cancelled, we should abort.
			if ctx.Err() != nil {
				return nil, ctx.Err()
			}
			dep := deps[packagePath(pkgPath)]
			if dep == nil {
				// We may be in GOPATH mode, in which case we need to check vendor dirs.
				searchDir := path.Dir(pkg.PkgPath())
				for {
					vdir := packagePath(path.Join(searchDir, "vendor", pkgPath))
					if vdep := deps[vdir]; vdep != nil {
						dep = vdep
						break
					}

					// Search until Dir doesn't take us anywhere new, e.g. "." or "/".
					next := path.Dir(searchDir)
					if searchDir == next {
						break
					}
					searchDir = next
				}
			}
			if dep == nil {
				return nil, errors.Errorf("no package for import %s", pkgPath)
			}
			depPkg, err := dep.check(ctx)
			if err != nil {
				return nil, err
			}
			pkg.imports[depPkg.pkgPath] = depPkg
			return depPkg.types, nil
		}),
	}
	check := types.NewChecker(cfg, fset, pkg.types, pkg.typesInfo)

	// Type checking errors are handled via the config, so ignore them here.
	_ = check.Files(files)
	// If the context was cancelled, we may have returned a ton of transient
	// errors to the type checker. Swallow them.
	if ctx.Err() != nil {
		return nil, ctx.Err()
	}

	// We don't care about a package's errors unless we have parsed it in full.
	if mode == source.ParseFull {
		for _, e := range rawErrors {
			srcErr, err := sourceError(ctx, fset, pkg, e)
			if err != nil {
				log.Error(ctx, "unable to compute error positions", err, telemetry.Package.Of(pkg.ID()))
				continue
			}
			pkg.errors = append(pkg.errors, srcErr)
		}
	}
	return pkg, nil
}

// An importFunc is an implementation of the single-method
// types.Importer interface based on a function value.
type importerFunc func(path string) (*types.Package, error)

func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
<commit_msg>internal/lsp/cache: don't type check types.Unsafe<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cache

import (
	"bytes"
	"context"
	"fmt"
	"go/ast"
	"go/token"
	"go/types"
	"path"
	"sort"
	"strings"
	"sync"

	"golang.org/x/tools/go/packages"
	"golang.org/x/tools/internal/lsp/source"
	"golang.org/x/tools/internal/lsp/telemetry"
	"golang.org/x/tools/internal/memoize"
	"golang.org/x/tools/internal/span"
	"golang.org/x/tools/internal/telemetry/log"
	"golang.org/x/tools/internal/telemetry/trace"
	errors "golang.org/x/xerrors"
)

type packageHandleKey string

// packageHandle implements source.PackageHandle.
type packageHandle struct {
	handle *memoize.Handle

	goFiles []source.ParseGoHandle

	// compiledGoFiles are the ParseGoHandles that compose the package.
	compiledGoFiles []source.ParseGoHandle

	// mode is the mode the the files were parsed in.
	mode source.ParseMode

	// m is the metadata associated with the package.
	m *metadata

	// key is the hashed key for the package.
	key packageHandleKey
}

func (ph *packageHandle) packageKey() packageKey {
	return packageKey{
		id:   ph.m.id,
		mode: ph.mode,
	}
}

// packageData contains the data produced by type-checking a package.
type packageData struct {
	memoize.NoCopy

	pkg *pkg
	err error
}

// buildPackageHandle returns a source.PackageHandle for a given package and config.
func (s *snapshot) buildPackageHandle(ctx context.Context, id packageID) (*packageHandle, error) {
	if ph := s.getPackage(id); ph != nil {
		return ph, nil
	}

	// Build the PackageHandle for this ID and its dependencies.
	ph, deps, err := s.buildKey(ctx, id)
	if err != nil {
		return nil, err
	}

	// Do not close over the packageHandle or the snapshot in the Bind function.
	// This creates a cycle, which causes the finalizers to never run on the handles.
	// The possible cycles are:
	//
	//     packageHandle.h.function -> packageHandle
	//     packageHandle.h.function -> snapshot -> packageHandle
	//

	m := ph.m
	mode := ph.mode
	goFiles := ph.goFiles
	compiledGoFiles := ph.compiledGoFiles
	key := ph.key
	fset := s.view.session.cache.fset

	h := s.view.session.cache.store.Bind(key, func(ctx context.Context) interface{} {
		// Begin loading the direct dependencies, in parallel.
		for _, dep := range deps {
			go func(dep *packageHandle) {
				dep.check(ctx)
			}(dep)
		}
		data := &packageData{}
		data.pkg, data.err = typeCheck(ctx, fset, m, mode, goFiles, compiledGoFiles, deps)
		return data
	})
	ph.handle = h

	// Cache the PackageHandle in the snapshot.
	s.addPackage(ph)

	return ph, nil
}

// buildKey computes the key for a given packageHandle.
func (s *snapshot) buildKey(ctx context.Context, id packageID) (*packageHandle, map[packagePath]*packageHandle, error) {
	m := s.getMetadata(id)
	if m == nil {
		return nil, nil, errors.Errorf("no metadata for %s", id)
	}
	mode := s.packageMode(id)
	goFiles, err := s.parseGoHandles(ctx, m.goFiles, mode)
	if err != nil {
		return nil, nil, err
	}
	compiledGoFiles, err := s.parseGoHandles(ctx, m.compiledGoFiles, mode)
	if err != nil {
		return nil, nil, err
	}
	ph := &packageHandle{
		m:               m,
		goFiles:         goFiles,
		compiledGoFiles: compiledGoFiles,
		mode:            mode,
	}
	// Make sure all of the depList are sorted.
	depList := append([]packageID{}, m.deps...)
	sort.Slice(depList, func(i, j int) bool {
		return depList[i] < depList[j]
	})

	deps := make(map[packagePath]*packageHandle)

	// Begin computing the key by getting the depKeys for all dependencies.
	var depKeys []packageHandleKey
	for _, depID := range depList {
		depHandle, err := s.buildPackageHandle(ctx, depID)
		if err != nil {
			log.Error(ctx, "no dep handle", err, telemetry.Package.Of(depID))

			// One bad dependency should not prevent us from checking the entire package.
			// Add a special key to mark a bad dependency.
			depKeys = append(depKeys, packageHandleKey(fmt.Sprintf("%s import not found", id)))
			continue
		}
		deps[depHandle.m.pkgPath] = depHandle
		depKeys = append(depKeys, depHandle.key)
	}
	ph.key = checkPackageKey(ph.m.id, ph.compiledGoFiles, m.config, depKeys)
	return ph, deps, nil
}

func checkPackageKey(id packageID, pghs []source.ParseGoHandle, cfg *packages.Config, deps []packageHandleKey) packageHandleKey {
	var depBytes []byte
	for _, dep := range deps {
		depBytes = append(depBytes, []byte(dep)...)
	}
	return packageHandleKey(hashContents([]byte(fmt.Sprintf("%s%s%s%s", id, hashParseKeys(pghs), hashConfig(cfg), hashContents(depBytes)))))
}

// hashConfig returns the hash for the *packages.Config.
func hashConfig(config *packages.Config) string {
	b := bytes.NewBuffer(nil)

	// Dir, Mode, Env, BuildFlags are the parts of the config that can change.
	b.WriteString(config.Dir)
	b.WriteString(string(config.Mode))

	for _, e := range config.Env {
		b.WriteString(e)
	}
	for _, f := range config.BuildFlags {
		b.WriteString(f)
	}
	return hashContents(b.Bytes())
}

func (ph *packageHandle) Check(ctx context.Context) (source.Package, error) {
	return ph.check(ctx)
}

func (ph *packageHandle) check(ctx context.Context) (*pkg, error) {
	v := ph.handle.Get(ctx)
	if v == nil {
		return nil, ctx.Err()
	}
	data := v.(*packageData)
	return data.pkg, data.err
}

func (ph *packageHandle) CompiledGoFiles() []source.ParseGoHandle {
	return ph.compiledGoFiles
}

func (ph *packageHandle) ID() string {
	return string(ph.m.id)
}

func (ph *packageHandle) MissingDependencies() []string {
	var md []string
	for i := range ph.m.missingDeps {
		md = append(md, string(i))
	}
	return md
}

func hashImports(ctx context.Context, wsPackages []source.PackageHandle) (string, error) {
	results := make(map[string]bool)
	var imports []string
	for _, ph := range wsPackages {
		// Check package since we do not always invalidate the metadata.
		pkg, err := ph.Check(ctx)
		if err != nil {
			return "", err
		}
		for _, path := range pkg.Imports() {
			imp := path.PkgPath()
			if _, ok := results[imp]; !ok {
				results[imp] = true
				imports = append(imports, imp)
			}
		}
	}
	sort.Strings(imports)
	hashed := strings.Join(imports, ",")
	return hashContents([]byte(hashed)), nil
}

func (ph *packageHandle) Cached() (source.Package, error) {
	return ph.cached()
}

func (ph *packageHandle) cached() (*pkg, error) {
	v := ph.handle.Cached()
	if v == nil {
		return nil, errors.Errorf("no cached type information for %s", ph.m.pkgPath)
	}
	data := v.(*packageData)
	return data.pkg, data.err
}

func (s *snapshot) parseGoHandles(ctx context.Context, files []span.URI, mode source.ParseMode) ([]source.ParseGoHandle, error) {
	phs := make([]source.ParseGoHandle, 0, len(files))
	for _, uri := range files {
		fh, err := s.GetFile(uri)
		if err != nil {
			return nil, err
		}
		phs = append(phs, s.view.session.cache.ParseGoHandle(fh, mode))
	}
	return phs, nil
}

func typeCheck(ctx context.Context, fset *token.FileSet, m *metadata, mode source.ParseMode, goFiles []source.ParseGoHandle, compiledGoFiles []source.ParseGoHandle, deps map[packagePath]*packageHandle) (*pkg, error) {
	ctx, done := trace.StartSpan(ctx, "cache.importer.typeCheck", telemetry.Package.Of(m.id))
	defer done()

	var rawErrors []error
	for _, err := range m.errors {
		rawErrors = append(rawErrors, err)
	}

	pkg := &pkg{
		id:              m.id,
		pkgPath:         m.pkgPath,
		mode:            mode,
		goFiles:         goFiles,
		compiledGoFiles: compiledGoFiles,
		imports:         make(map[packagePath]*pkg),
		typesSizes:      m.typesSizes,
		typesInfo: &types.Info{
			Types:      make(map[ast.Expr]types.TypeAndValue),
			Defs:       make(map[*ast.Ident]types.Object),
			Uses:       make(map[*ast.Ident]types.Object),
			Implicits:  make(map[ast.Node]types.Object),
			Selections: make(map[*ast.SelectorExpr]*types.Selection),
			Scopes:     make(map[ast.Node]*types.Scope),
		},
		forTest: m.forTest,
	}
	var (
		files        = make([]*ast.File, len(pkg.compiledGoFiles))
		parseErrors  = make([]error, len(pkg.compiledGoFiles))
		actualErrors = make([]error, len(pkg.compiledGoFiles))
		wg           sync.WaitGroup
	)
	for i, ph := range pkg.compiledGoFiles {
		wg.Add(1)
		go func(i int, ph source.ParseGoHandle) {
			files[i], _, parseErrors[i], actualErrors[i] = ph.Parse(ctx)
			wg.Done()
		}(i, ph)
	}
	for _, ph := range pkg.goFiles {
		wg.Add(1)
		// We need to parse the non-compiled go files, but we don't care about their errors.
		go func(ph source.ParseGoHandle) {
			ph.Parse(ctx)
			wg.Done()
		}(ph)
	}
	wg.Wait()

	for _, e := range parseErrors {
		if e != nil {
			rawErrors = append(rawErrors, e)
		}
	}

	var i int
	for _, f := range files {
		if f != nil {
			files[i] = f
			i++
		}
	}
	files = files[:i]

	// Use the default type information for the unsafe package.
	if pkg.pkgPath == "unsafe" {
		pkg.types = types.Unsafe
		// Don't type check Unsafe: it's unnecessary, and doing so exposes a data
		// race to Unsafe.completed.
		return pkg, nil
	} else if len(files) == 0 { // not the unsafe package, no parsed files
		return nil, errors.Errorf("no parsed files for package %s, expected: %s, errors: %v, list errors: %v", pkg.pkgPath, pkg.compiledGoFiles, actualErrors, rawErrors)
	} else {
		pkg.types = types.NewPackage(string(m.pkgPath), m.name)
	}

	cfg := &types.Config{
		Error: func(e error) {
			rawErrors = append(rawErrors, e)
		},
		Importer: importerFunc(func(pkgPath string) (*types.Package, error) {
			// If the context was cancelled, we should abort.
			if ctx.Err() != nil {
				return nil, ctx.Err()
			}
			dep := deps[packagePath(pkgPath)]
			if dep == nil {
				// We may be in GOPATH mode, in which case we need to check vendor dirs.
				searchDir := path.Dir(pkg.PkgPath())
				for {
					vdir := packagePath(path.Join(searchDir, "vendor", pkgPath))
					if vdep := deps[vdir]; vdep != nil {
						dep = vdep
						break
					}

					// Search until Dir doesn't take us anywhere new, e.g. "." or "/".
					next := path.Dir(searchDir)
					if searchDir == next {
						break
					}
					searchDir = next
				}
			}
			if dep == nil {
				return nil, errors.Errorf("no package for import %s", pkgPath)
			}
			depPkg, err := dep.check(ctx)
			if err != nil {
				return nil, err
			}
			pkg.imports[depPkg.pkgPath] = depPkg
			return depPkg.types, nil
		}),
	}
	check := types.NewChecker(cfg, fset, pkg.types, pkg.typesInfo)

	// Type checking errors are handled via the config, so ignore them here.
	_ = check.Files(files)
	// If the context was cancelled, we may have returned a ton of transient
	// errors to the type checker. Swallow them.
	if ctx.Err() != nil {
		return nil, ctx.Err()
	}

	// We don't care about a package's errors unless we have parsed it in full.
	if mode == source.ParseFull {
		for _, e := range rawErrors {
			srcErr, err := sourceError(ctx, fset, pkg, e)
			if err != nil {
				log.Error(ctx, "unable to compute error positions", err, telemetry.Package.Of(pkg.ID()))
				continue
			}
			pkg.errors = append(pkg.errors, srcErr)
		}
	}
	return pkg, nil
}

// An importFunc is an implementation of the single-method
// types.Importer interface based on a function value.
type importerFunc func(path string) (*types.Package, error)

func (f importerFunc) Import(path string) (*types.Package, error) { return f(path) }
<|endoftext|>


<commit_before>package task

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/go-task/task/execext"

	"github.com/spf13/pflag"
	"golang.org/x/sync/errgroup"
)

var (
	// TaskFilePath is the default Taskfile
	TaskFilePath = "Taskfile"

	// Force (--force or -f flag) forces a task to run even when it's up-to-date
	Force bool
	// Watch (--watch or -w flag) enables watch of a task
	Watch bool

	// Tasks constains the tasks parsed from Taskfile
	Tasks = make(map[string]*Task)
)

// Task represents a task
type Task struct {
	Cmds      []string
	Deps      []string
	Desc      string
	Sources   []string
	Generates []string
	Status    []string
	Dir       string
	Vars      map[string]string
	Set       string
	Env       map[string]string
}

// Run runs Task
func Run() {
	log.SetFlags(0)

	args := pflag.Args()
	if len(args) == 0 {
		log.Println("task: No argument given, trying default task")
		args = []string{"default"}
	}

	var err error
	Tasks, err = readTaskfile()
	if err != nil {
		log.Fatal(err)
	}

	if HasCyclicDep(Tasks) {
		log.Fatal("Cyclic dependency detected")
	}

	// check if given tasks exist
	for _, a := range args {
		if _, ok := Tasks[a]; !ok {
			var err error = &taskNotFoundError{taskName: a}
			fmt.Println(err)
			printExistingTasksHelp()
			return
		}
	}

	if Watch {
		if err := WatchTasks(args); err != nil {
			log.Fatal(err)
		}
		return
	}

	for _, a := range args {
		if err = RunTask(context.Background(), a); err != nil {
			log.Fatal(err)
		}
	}
}

// RunTask runs a task by its name
func RunTask(ctx context.Context, name string) error {
	t, ok := Tasks[name]
	if !ok {
		return &taskNotFoundError{name}
	}

	if err := t.runDeps(ctx); err != nil {
		return err
	}

	if !Force {
		upToDate, err := t.isUpToDate()
		if err != nil {
			return err
		}
		if upToDate {
			log.Printf(`task: Task "%s" is up to date`, name)
			return nil
		}
	}

	for i := range t.Cmds {
		if err := t.runCommand(ctx, i); err != nil {
			return &taskRunError{name, err}
		}
	}
	return nil
}

func (t *Task) runDeps(ctx context.Context) error {
	g, ctx := errgroup.WithContext(ctx)

	for _, d := range t.Deps {
		dep := d

		g.Go(func() error {
			dep, err := t.ReplaceVariables(dep)
			if err != nil {
				return err
			}

			if err = RunTask(ctx, dep); err != nil {
				return err
			}
			return nil
		})
	}

	if err := g.Wait(); err != nil {
		return err
	}
	return nil
}

func (t *Task) isUpToDate() (bool, error) {
	if len(t.Status) > 0 {
		environ, err := t.getEnviron()
		if err != nil {
			return false, err
		}

		for _, s := range t.Status {
			err = execext.RunCommand(&execext.RunCommandOptions{
				Command: s,
				Dir:     t.Dir,
				Env:     environ,
			})
			if err != nil {
				return false, nil
			}
		}
		return true, nil
	}

	if len(t.Sources) == 0 || len(t.Generates) == 0 {
		return false, nil
	}

	sources, err := t.ReplaceSliceVariables(t.Sources)
	if err != nil {
		return false, err
	}
	generates, err := t.ReplaceSliceVariables(t.Generates)
	if err != nil {
		return false, err
	}

	sourcesMaxTime, err := getPatternsMaxTime(sources)
	if err != nil || sourcesMaxTime.IsZero() {
		return false, nil
	}

	generatesMinTime, err := getPatternsMinTime(generates)
	if err != nil || generatesMinTime.IsZero() {
		return false, nil
	}

	return generatesMinTime.After(sourcesMaxTime), nil
}

func (t *Task) runCommand(ctx context.Context, i int) error {
	c, err := t.ReplaceVariables(t.Cmds[i])
	if err != nil {
		return err
	}

	if strings.HasPrefix(c, "^") {
		c = strings.TrimPrefix(c, "^")
		if err = RunTask(ctx, c); err != nil {
			return err
		}
		return nil
	}

	dir, err := t.ReplaceVariables(t.Dir)
	if err != nil {
		return err
	}

	envs, err := t.getEnviron()
	if err != nil {
		return err
	}
	opts := &execext.RunCommandOptions{
		Context: ctx,
		Command: c,
		Dir:     dir,
		Env:     envs,
		Stdin:   os.Stdin,
		Stderr:  os.Stderr,
	}

	if t.Set == "" {
		log.Println(c)
		opts.Stdout = os.Stdout
		if err = execext.RunCommand(opts); err != nil {
			return err
		}
	} else {
		buff := bytes.NewBuffer(nil)
		opts.Stdout = buff
		if err = execext.RunCommand(opts); err != nil {
			return err
		}
		os.Setenv(t.Set, strings.TrimSpace(buff.String()))
	}
	return nil
}

func (t *Task) getEnviron() ([]string, error) {
	if t.Env == nil {
		return nil, nil
	}

	envs := os.Environ()

	for k, v := range t.Env {
		env, err := t.ReplaceVariables(fmt.Sprintf("%s=%s", k, v))
		if err != nil {
			return nil, err
		}
		envs = append(envs, env)
	}
	return envs, nil
}
<commit_msg>Use context on status commands<commit_after>package task

import (
	"bytes"
	"context"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/go-task/task/execext"

	"github.com/spf13/pflag"
	"golang.org/x/sync/errgroup"
)

var (
	// TaskFilePath is the default Taskfile
	TaskFilePath = "Taskfile"

	// Force (--force or -f flag) forces a task to run even when it's up-to-date
	Force bool
	// Watch (--watch or -w flag) enables watch of a task
	Watch bool

	// Tasks constains the tasks parsed from Taskfile
	Tasks = make(map[string]*Task)
)

// Task represents a task
type Task struct {
	Cmds      []string
	Deps      []string
	Desc      string
	Sources   []string
	Generates []string
	Status    []string
	Dir       string
	Vars      map[string]string
	Set       string
	Env       map[string]string
}

// Run runs Task
func Run() {
	log.SetFlags(0)

	args := pflag.Args()
	if len(args) == 0 {
		log.Println("task: No argument given, trying default task")
		args = []string{"default"}
	}

	var err error
	Tasks, err = readTaskfile()
	if err != nil {
		log.Fatal(err)
	}

	if HasCyclicDep(Tasks) {
		log.Fatal("Cyclic dependency detected")
	}

	// check if given tasks exist
	for _, a := range args {
		if _, ok := Tasks[a]; !ok {
			var err error = &taskNotFoundError{taskName: a}
			fmt.Println(err)
			printExistingTasksHelp()
			return
		}
	}

	if Watch {
		if err := WatchTasks(args); err != nil {
			log.Fatal(err)
		}
		return
	}

	for _, a := range args {
		if err = RunTask(context.Background(), a); err != nil {
			log.Fatal(err)
		}
	}
}

// RunTask runs a task by its name
func RunTask(ctx context.Context, name string) error {
	t, ok := Tasks[name]
	if !ok {
		return &taskNotFoundError{name}
	}

	if err := t.runDeps(ctx); err != nil {
		return err
	}

	if !Force {
		upToDate, err := t.isUpToDate(ctx)
		if err != nil {
			return err
		}
		if upToDate {
			log.Printf(`task: Task "%s" is up to date`, name)
			return nil
		}
	}

	for i := range t.Cmds {
		if err := t.runCommand(ctx, i); err != nil {
			return &taskRunError{name, err}
		}
	}
	return nil
}

func (t *Task) runDeps(ctx context.Context) error {
	g, ctx := errgroup.WithContext(ctx)

	for _, d := range t.Deps {
		dep := d

		g.Go(func() error {
			dep, err := t.ReplaceVariables(dep)
			if err != nil {
				return err
			}

			if err = RunTask(ctx, dep); err != nil {
				return err
			}
			return nil
		})
	}

	if err := g.Wait(); err != nil {
		return err
	}
	return nil
}

func (t *Task) isUpToDate(ctx context.Context) (bool, error) {
	if len(t.Status) > 0 {
		environ, err := t.getEnviron()
		if err != nil {
			return false, err
		}

		for _, s := range t.Status {
			err = execext.RunCommand(&execext.RunCommandOptions{
				Context: ctx,
				Command: s,
				Dir:     t.Dir,
				Env:     environ,
			})
			if err != nil {
				return false, nil
			}
		}
		return true, nil
	}

	if len(t.Sources) == 0 || len(t.Generates) == 0 {
		return false, nil
	}

	sources, err := t.ReplaceSliceVariables(t.Sources)
	if err != nil {
		return false, err
	}
	generates, err := t.ReplaceSliceVariables(t.Generates)
	if err != nil {
		return false, err
	}

	sourcesMaxTime, err := getPatternsMaxTime(sources)
	if err != nil || sourcesMaxTime.IsZero() {
		return false, nil
	}

	generatesMinTime, err := getPatternsMinTime(generates)
	if err != nil || generatesMinTime.IsZero() {
		return false, nil
	}

	return generatesMinTime.After(sourcesMaxTime), nil
}

func (t *Task) runCommand(ctx context.Context, i int) error {
	c, err := t.ReplaceVariables(t.Cmds[i])
	if err != nil {
		return err
	}

	if strings.HasPrefix(c, "^") {
		c = strings.TrimPrefix(c, "^")
		if err = RunTask(ctx, c); err != nil {
			return err
		}
		return nil
	}

	dir, err := t.ReplaceVariables(t.Dir)
	if err != nil {
		return err
	}

	envs, err := t.getEnviron()
	if err != nil {
		return err
	}
	opts := &execext.RunCommandOptions{
		Context: ctx,
		Command: c,
		Dir:     dir,
		Env:     envs,
		Stdin:   os.Stdin,
		Stderr:  os.Stderr,
	}

	if t.Set == "" {
		log.Println(c)
		opts.Stdout = os.Stdout
		if err = execext.RunCommand(opts); err != nil {
			return err
		}
	} else {
		buff := bytes.NewBuffer(nil)
		opts.Stdout = buff
		if err = execext.RunCommand(opts); err != nil {
			return err
		}
		os.Setenv(t.Set, strings.TrimSpace(buff.String()))
	}
	return nil
}

func (t *Task) getEnviron() ([]string, error) {
	if t.Env == nil {
		return nil, nil
	}

	envs := os.Environ()

	for k, v := range t.Env {
		env, err := t.ReplaceVariables(fmt.Sprintf("%s=%s", k, v))
		if err != nil {
			return nil, err
		}
		envs = append(envs, env)
	}
	return envs, nil
}
<|endoftext|>


<commit_before>package beam

import (
	"fmt"
	"sync"
)

func Task(f func(in Receiver, out Sender)) Sender {
	var running bool
	var l sync.RWMutex
	inR, inW := Pipe()
	outR, outW := Pipe()
	obj := NewServer()
	obj.OnAttach(Handler(func(msg *Message) error {
		msg.Ret.Send(&Message{Name: "ack", Ret: inW})
		go func() {
			fmt.Printf("copying task output from %#v to %#v\n", outR, msg.Ret)
			defer fmt.Printf("(DONE) copying task output from %#v to %#v\n", outR, msg.Ret)
			Copy(msg.Ret, outR)
			msg.Ret.Close()
		}()
		return nil
	}))
	obj.OnStart(Handler(func(msg *Message) error {
		l.RLock()
		r := running
		l.RUnlock()
		if r {
			return fmt.Errorf("already running")
		}
		l.Lock()
		go f(inR, outW)
		running = true
		l.Unlock()
		msg.Ret.Send(&Message{Name: "ack"})
		return nil
	}))
	return obj
}
<commit_msg>Remove extra goroutine wrapper in task.go<commit_after>package beam

import (
	"fmt"
	"sync"
)

func Task(f func(in Receiver, out Sender)) Sender {
	var running bool
	var l sync.RWMutex
	inR, inW := Pipe()
	outR, outW := Pipe()
	obj := NewServer()
	obj.OnAttach(Handler(func(msg *Message) error {
		msg.Ret.Send(&Message{Name: "ack", Ret: inW})
		fmt.Printf("copying task output from %#v to %#v\n", outR, msg.Ret)
		defer fmt.Printf("(DONE) copying task output from %#v to %#v\n", outR, msg.Ret)
		Copy(msg.Ret, outR)
		return nil
	}))
	obj.OnStart(Handler(func(msg *Message) error {
		l.RLock()
		r := running
		l.RUnlock()
		if r {
			return fmt.Errorf("already running")
		}
		l.Lock()
		go f(inR, outW)
		running = true
		l.Unlock()
		msg.Ret.Send(&Message{Name: "ack"})
		return nil
	}))
	return obj
}
<|endoftext|>


<commit_before>package status

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Checksum validades if a task is up to date by calculating its source
// files checksum
type Checksum struct {
	Dir     string
	Task    string
	Sources []string
}

// IsUpToDate implements the Checker interface
func (c *Checksum) IsUpToDate() (bool, error) {
	checksumFile := c.checksumFilePath()

	data, _ := ioutil.ReadFile(checksumFile)
	oldMd5 := strings.TrimSpace(string(data))

	sources, err := glob(c.Dir, c.Sources)
	if err != nil {
		return false, err
	}

	newMd5, err := c.checksum(sources...)
	if err != nil {
		return false, nil
	}

	_ = os.MkdirAll(filepath.Join(c.Dir, ".task"), 0755)
	if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
		return false, err
	}
	return oldMd5 == newMd5, nil
}

func (c *Checksum) checksum(files ...string) (string, error) {
	h := md5.New()

	for _, f := range files {
		f, err := os.Open(f)
		if err != nil {
			return "", err
		}
		info, err := f.Stat()
		if err != nil {
			return "", err
		}
		if info.IsDir() {
			continue
		}
		if _, err := io.Copy(h, f); err != nil {
			return "", err
		}
	}

	return fmt.Sprintf("%x", h.Sum(nil)), nil
}

// OnError implements the Checker interface
func (c *Checksum) OnError() error {
	return os.Remove(c.checksumFilePath())
}

func (c *Checksum) checksumFilePath() string {
	return filepath.Join(c.Dir, ".task", c.normalizeFilename(c.Task))
}

var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")

// replaces invalid caracters on filenames with "-"
func (*Checksum) normalizeFilename(f string) string {
	return checksumFilenameRegexp.ReplaceAllString(f, "-")
}
<commit_msg>checksum: also sum the name of the files, so it changes after renaming<commit_after>package status

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Checksum validades if a task is up to date by calculating its source
// files checksum
type Checksum struct {
	Dir     string
	Task    string
	Sources []string
}

// IsUpToDate implements the Checker interface
func (c *Checksum) IsUpToDate() (bool, error) {
	checksumFile := c.checksumFilePath()

	data, _ := ioutil.ReadFile(checksumFile)
	oldMd5 := strings.TrimSpace(string(data))

	sources, err := glob(c.Dir, c.Sources)
	if err != nil {
		return false, err
	}

	newMd5, err := c.checksum(sources...)
	if err != nil {
		return false, nil
	}

	_ = os.MkdirAll(filepath.Join(c.Dir, ".task"), 0755)
	if err = ioutil.WriteFile(checksumFile, []byte(newMd5+"\n"), 0644); err != nil {
		return false, err
	}
	return oldMd5 == newMd5, nil
}

func (c *Checksum) checksum(files ...string) (string, error) {
	h := md5.New()

	for _, f := range files {
		f, err := os.Open(f)
		if err != nil {
			return "", err
		}
		info, err := f.Stat()
		if err != nil {
			return "", err
		}
		if info.IsDir() {
			continue
		}
		// also sum the filename, so checksum changes for renaming a file
		if _, err = io.Copy(h, strings.NewReader(info.Name())); err != nil {
			return "", err
		}
		if _, err = io.Copy(h, f); err != nil {
			return "", err
		}
	}

	return fmt.Sprintf("%x", h.Sum(nil)), nil
}

// OnError implements the Checker interface
func (c *Checksum) OnError() error {
	return os.Remove(c.checksumFilePath())
}

func (c *Checksum) checksumFilePath() string {
	return filepath.Join(c.Dir, ".task", c.normalizeFilename(c.Task))
}

var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")

// replaces invalid caracters on filenames with "-"
func (*Checksum) normalizeFilename(f string) string {
	return checksumFilenameRegexp.ReplaceAllString(f, "-")
}
<|endoftext|>


<commit_before>package main

import (
	"os/exec"
	"time"
)

const (
	taskActionStart  = iota
	taskActionStop   = iota
	taskActionStatus = iota
)

const (
	TaskStatusStopped    = iota
	TaskStatusRunning    = iota
	TaskStatusRestarting = iota
)

// A Task runs an executable in the background. Tasks each have their own background loop.
// While a task's background loop is running, its fields should not be modified.
type Task struct {
	Args     []string
	AutoRun  bool
	Dir      string
	Env      map[string]string
	GID      int
	Interval int
	UID      int
	Relaunch bool
	SetGID   bool
	SetUID   bool

	actions chan<- taskAction
}

// NewTask creates an empty task. The task's background loop will not be running
// until StartLoop() is called.
func NewTask() *Task {
	return &Task{}
}

// Start begins executing a command for the task. If the task is executing, this
// has no effect.
func (t *Task) Start() {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStart, resp}
	<-resp
}

// StartLoop starts the task's background Goroutine. You must call this before
// using the Start(), Stop(), and Status() methods.
func (t *Task) StartLoop() {
	if t.actions != nil {
		panic("task's loop is already running")
	}
	ch := make(chan taskAction)
	t.actions = ch
	go t.loop(ch)
}

// Status returns the task's current state. Possible values are
// TaskStatusStopped, TaskStatusRunning, and TaskStatusRestarting.
func (t *Task) Status() int {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStatus, resp}
	return (<-resp).(int)
}

// Stop terminates the task's command. If the task is not executing, this has no
// effect. This blocks to wait for the task to stop executing.
func (t *Task) Stop() {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStop, resp}
	<-resp
}

// StopLoop stops a task's background Goroutine. You must call this after you
// are done using a task.
//
// If the task is executing, this will terminate the process and block until it
// has stopped.
func (t *Task) StopLoop() {
	if t.actions == nil {
		panic("task's loop is not running")
	}
	t.Stop()
	close(t.actions)
	t.actions = nil
}

func (t *Task) cmd() *exec.Cmd {
	task := exec.Command(t.Args[0], t.Args[1:]...)
	for key, value := range t.Env {
		task.Env = append(task.Env, key+"="+value)
	}
	task.Dir = t.Dir

	// TODO: here, set UID and GID

	return task
}

func (t *Task) loop(actions <-chan taskAction) {
	for {
		if val, ok := <-actions; !ok {
			return
		} else if val.action == taskActionStatus {
			val.resp <- TaskStatusStopped
		} else if val.action == taskActionStart {
			close(val.resp)
			if t.Relaunch {
				t.runRestart(actions)
			} else {
				t.runOnce(actions)
			}
		} else {
			close(val.resp)
		}
	}
}

func (t *Task) runOnce(actions <-chan taskAction) {
	cmd := t.cmd()
	if err := cmd.Start(); err != nil {
		return
	}

	doneChan := make(chan struct{})
	go func() {
		cmd.Wait()
		close(doneChan)
	}()

	// Wait for commands or termination.
	for {
		select {
		case <-doneChan:
			return
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				cmd.Process.Kill()
				// Wait for the task to die before closing the response channel.
				<-doneChan
				if ok {
					close(val.resp)
				}
				return
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRunning
			} else {
				close(val.resp)
			}
		}
	}
}

func (t *Task) runRestart(actions <-chan taskAction) {
	cmd := t.cmd()
	if err := cmd.Start(); err != nil {
		return
	}

	// Wait for termination in the background
	doneChan := make(chan struct{})
	go func() {
		cmd.Wait()
		doneChan <- struct{}{}
	}()

	// Wait for commands and restart the task if it stops.
	for {
		select {
		case <-doneChan:
			// Wait for the timeout and then start again.
			if !t.waitTimeout(actions) {
				return
			}
			// TODO: see if I need to re-create cmd every time.
			cmd = t.cmd()
			go func() {
				cmd.Run()
				doneChan <- struct{}{}
			}()
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				cmd.Process.Kill()
				// Wait for the task to die before closing the response channel.
				<-doneChan
				if ok {
					close(val.resp)
				}
				return
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRunning
			} else {
				close(val.resp)
			}
		}
	}
}

func (t *Task) waitTimeout(actions <-chan taskAction) bool {
	for {
		select {
		case <-time.After(time.Second * time.Duration(t.Interval)):
			return true
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				if ok {
					close(val.resp)
				}
				return false
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRestarting
			} else if val.action == taskActionStart {
				close(val.resp)
				return true
			}
		}
	}
}

type taskAction struct {
	action int
	resp   chan<- interface{}
}
<commit_msg>drafted logging for tasks<commit_after>package main

import (
	"bytes"
	"os/exec"
	"sync"
	"time"
)

const (
	taskActionStart  = iota
	taskActionStop   = iota
	taskActionStatus = iota
)

const (
	TaskStatusStopped    = iota
	TaskStatusRunning    = iota
	TaskStatusRestarting = iota
)

const MaxBacklogSize = 50000

const (
	BacklogLineStdout = iota
	BacklogLineStderr = iota
	BacklogLineStatus = iota
)

// A BacklogLine represents a line of output from a task. A line can be a normal line of output, a
// status message, or an error from stdout.
type BacklogLine struct {
	// Type is either BacklogLineStdout, BacklogLineStatus, or BacklogLineStderr.
	Type int

	// Data is the actual message that was output by the task.
	Data string

	// Time is the UNIX timestamp in milliseconds when the message was logged.
	Time int64
}

// A Task runs an executable in the background. Tasks each have their own background loop.
// While a task's background loop is running, its fields should not be modified.
type Task struct {
	Args     []string
	AutoRun  bool
	Dir      string
	Env      map[string]string
	GID      int
	Interval int
	UID      int
	Relaunch bool
	SetGID   bool
	SetUID   bool

	backlogLock sync.RWMutex
	backlog     []BacklogLine

	actions chan<- taskAction
}

// NewTask creates an empty task. The task's background loop will not be running
// until StartLoop() is called.
func NewTask() *Task {
	return &Task{}
}

// Backlog returns a copy of the command's backlog.
func (t *Task) Backlog() []BacklogLine {
	t.backlogLock.RLock()
	defer t.backlogLock.RUnlock()
	backlog := make([]BacklogLine, len(t.backlog))
	for i, x := range t.backlog {
		backlog[i] = x
	}
	return backlog
}

// Start begins executing a command for the task. If the task is executing, this
// has no effect.
func (t *Task) Start() {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStart, resp}
	<-resp
}

// StartLoop starts the task's background Goroutine. You must call this before
// using the Start(), Stop(), and Status() methods.
func (t *Task) StartLoop() {
	if t.actions != nil {
		panic("task's loop is already running")
	}
	ch := make(chan taskAction)
	t.actions = ch
	go t.loop(ch)
}

// Status returns the task's current state. Possible values are
// TaskStatusStopped, TaskStatusRunning, and TaskStatusRestarting.
func (t *Task) Status() int {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStatus, resp}
	return (<-resp).(int)
}

// Stop terminates the task's command. If the task is not executing, this has no
// effect. This blocks to wait for the task to stop executing.
func (t *Task) Stop() {
	resp := make(chan interface{})
	t.actions <- taskAction{taskActionStop, resp}
	<-resp
}

// StopLoop stops a task's background Goroutine. You must call this after you
// are done using a task.
//
// If the task is executing, this will terminate the process and block until it
// has stopped.
func (t *Task) StopLoop() {
	if t.actions == nil {
		panic("task's loop is not running")
	}
	t.Stop()
	close(t.actions)
	t.actions = nil
}

func (t *Task) cmd() *exec.Cmd {
	task := exec.Command(t.Args[0], t.Args[1:]...)
	for key, value := range t.Env {
		task.Env = append(task.Env, key+"="+value)
	}
	task.Dir = t.Dir

	// TODO: here, set UID and GID

	return task
}

func (t *Task) generateStreams(cmd *exec.Cmd, doneChan <-chan struct{}) {
	stdoutStream := make(chan string)
	stderrStream := make(chan string)
	stdout := &lineForwarder{sendTo: stdoutStream}
	stderr := &lineForwarder{sendTo: stderrStream}
	cmd.Stdout = stdout
	cmd.Stderr = stderr
	go func() {
	Loop:
		for {
			select {
			case line := <-stdoutStream:
				t.pushBacklog(BacklogLineStdout, line)
			case line := <-stderrStream:
				t.pushBacklog(BacklogLineStderr, line)
			case <-doneChan:
				break Loop
			}
		}
		stdout.FlushIfNotEmpty()
		stderr.FlushIfNotEmpty()
	MissedItemLoop:
		for {
			select {
			case line := <-stdoutStream:
				t.pushBacklog(BacklogLineStdout, line)
			case line := <-stderrStream:
				t.pushBacklog(BacklogLineStderr, line)
			default:
				break MissedItemLoop
			}
		}
	}()
}

func (t *Task) loop(actions <-chan taskAction) {
	for {
		if val, ok := <-actions; !ok {
			return
		} else if val.action == taskActionStatus {
			val.resp <- TaskStatusStopped
		} else if val.action == taskActionStart {
			close(val.resp)
			if t.Relaunch {
				t.runRestart(actions)
			} else {
				t.runOnce(actions)
			}
		} else {
			close(val.resp)
		}
	}
}

func (t *Task) pushBacklog(typeNum int, data string) {
	line := BacklogLine{typeNum, data, time.Now().UnixNano() / 1000000}
	t.backlogLock.Lock()
	if len(t.backlog) < MaxBacklogSize {
		t.backlog = append(t.backlog, line)
	} else {
		for i := 1; i < len(t.backlog); i++ {
			t.backlog[i-1] = t.backlog[i]
		}
		t.backlog[MaxBacklogSize-1] = line
	}
	t.backlogLock.Unlock()
}

func (t *Task) runOnce(actions <-chan taskAction) {
	doneChan := make(chan struct{})
	cmd := t.cmd()
	t.generateStreams(cmd, doneChan)

	if err := cmd.Start(); err != nil {
		t.pushBacklog(BacklogLineStatus, "error starting: "+err.Error())
		return
	}

	t.pushBacklog(BacklogLineStatus, "started task")

	go func() {
		cmd.Wait()
		close(doneChan)
	}()

	for {
		select {
		case <-doneChan:
			t.pushBacklog(BacklogLineStatus, "task exited")
			return
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "task stopped")
				cmd.Process.Kill()
				// Wait for the task to die before closing the response channel.
				<-doneChan
				if ok {
					close(val.resp)
				}
				return
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRunning
			} else {
				close(val.resp)
			}
		}
	}
}

func (t *Task) runRestart(actions <-chan taskAction) {
	doneChan := make(chan struct{})
	cmd := t.cmd()
	t.generateStreams(cmd, doneChan)

	if err := cmd.Start(); err != nil {
		t.pushBacklog(BacklogLineStatus, "error starting: "+err.Error())
		return
	}

	t.pushBacklog(BacklogLineStatus, "started task")

	go func() {
		cmd.Wait()
		close(doneChan)
	}()

	for {
		select {
		case <-doneChan:
			t.pushBacklog(BacklogLineStatus, "task exited; waiting to restart")

			if !t.waitTimeout(actions) {
				return
			}

			t.pushBacklog(BacklogLineStatus, "restarting...")
			cmd = t.cmd()
			doneChan = make(chan struct{})
			t.generateStreams(cmd, doneChan)
			go func() {
				if err := cmd.Run(); err != nil {
					t.pushBacklog(BacklogLineStatus, "error restarting: "+err.Error())
				}
				close(doneChan)
			}()
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "task stopped")
				cmd.Process.Kill()
				// Wait for the task to die before closing the response channel.
				<-doneChan
				if ok {
					close(val.resp)
				}
				return
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRunning
			} else {
				close(val.resp)
			}
		}
	}
}

func (t *Task) waitTimeout(actions <-chan taskAction) bool {
	for {
		select {
		case <-time.After(time.Second * time.Duration(t.Interval)):
			return true
		case val, ok := <-actions:
			if !ok || val.action == taskActionStop {
				t.pushBacklog(BacklogLineStatus, "stopped during relaunch")
				if ok {
					close(val.resp)
				}
				return false
			} else if val.action == taskActionStatus {
				val.resp <- TaskStatusRestarting
			} else if val.action == taskActionStart {
				t.pushBacklog(BacklogLineStatus, "relaunch wait bypassed")
				close(val.resp)
				return true
			}
		}
	}
}

type taskAction struct {
	action int
	resp   chan<- interface{}
}

// A lineForwarder is an io.Writer which buffers lines and sends them over a channel.
type lineForwarder struct {
	sendTo chan<- string
	buffer bytes.Buffer
}

func (l *lineForwarder) FlushIfNotEmpty() {
	if l.buffer.Len() > 0 {
		l.FlushLine()
	}
}

func (l *lineForwarder) FlushLine() {
	l.sendTo <- l.buffer.String()
	l.buffer.Reset()
}

func (l *lineForwarder) Write(p []byte) (n int, err error) {
	for _, ch := range p {
		if ch == '\n' {
			l.FlushLine()
		} else {
			l.buffer.WriteByte(ch)
		}
	}
	return len(p), nil
}
<|endoftext|>


<commit_before>package util

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"time"

	"github.com/getlantern/fronted"
	"github.com/getlantern/golog"
	"github.com/getlantern/keyman"
	"github.com/getlantern/waitforserver"
)

const (
	defaultAddr = "127.0.0.1:8787"
)

var (
	log = golog.LoggerFor("flashlight.util")

	// This is for doing direct domain fronting if necessary. We store this as
	// an instance variable because it caches TLS session configs.
	direct = fronted.NewDirect()
)

// HTTPFetcher is a simple interface for types that are able to fetch data over HTTP.
type HTTPFetcher interface {
	Do(req *http.Request) (*http.Response, error)
}

func success(resp *http.Response) bool {
	return resp.StatusCode > 199 && resp.StatusCode < 400
}

// NewChainedAndFronted creates a new struct for accessing resources using chained
// and direct fronted servers in parallel.
func NewChainedAndFronted() *chainedAndFronted {
	cf := &chainedAndFronted{}
	cf.fetcher = &dualFetcher{cf}
	return cf
}

// ChainedAndFronted fetches HTTP data in parallel using both chained and fronted
// servers.
type chainedAndFronted struct {
	fetcher HTTPFetcher
}

// Do will attempt to execute the specified HTTP request using only a chained fetcher
func (cf *chainedAndFronted) Do(req *http.Request) (*http.Response, error) {
	resp, err := cf.fetcher.Do(req)
	if err != nil {
		// If there's an error, switch back to using the dual fetcher.
		cf.fetcher = &dualFetcher{cf}
	} else if !success(resp) {
		cf.fetcher = &dualFetcher{cf}
	}
	return resp, err
}

type chainedFetcher struct {
}

// Do will attempt to execute the specified HTTP request using only a chained fetcher
func (cf *chainedFetcher) Do(req *http.Request) (*http.Response, error) {
	log.Debugf("Using chained fronter")
	if client, err := HTTPClient("", defaultAddr); err != nil {
		log.Errorf("Could not create HTTP client: %v", err)
		return nil, err
	} else {
		return client.Do(req)
	}
}

type dualFetcher struct {
	cf *chainedAndFronted
}

// Do will attempt to execute the specified HTTP request using both
// chained and fronted servers, simply returning the first response to
// arrive. Callers MUST use the Lantern-Fronted-URL HTTP header to
// specify the fronted URL to use.
func (df *dualFetcher) Do(req *http.Request) (*http.Response, error) {
	log.Debugf("Using dual fronter")
	frontedUrl := req.Header.Get("Lantern-Fronted-URL")
	req.Header.Del("Lantern-Fronted-URL")

	if frontedUrl == "" {
		return nil, errors.New("Callers MUST specify the fronted URL in the Lantern-Fronted-URL header")
	}
	responses := make(chan *http.Response, 2)
	errs := make(chan error, 2)

	request := func(client HTTPFetcher, req *http.Request) error {
		if resp, err := client.Do(req); err != nil {
			log.Errorf("Could not complete request with: %v, %v", frontedUrl, err)
			errs <- err
			return err
		} else {
			if success(resp) {
				log.Debugf("Got successful HTTP call!")
				responses <- resp
				return nil
			} else {
				// If the local proxy can't connect to any upstread proxies, for example,
				// it will return a 502.
				err := fmt.Errorf("Bad response code: %v", resp.StatusCode)
				errs <- err
				return err
			}
		}
	}

	go func() {
		if req, err := http.NewRequest("GET", frontedUrl, nil); err != nil {
			log.Errorf("Could not create request for: %v, %v", frontedUrl, err)
			errs <- err
		} else {
			log.Debug("Sending request via DDF")
			if err := request(direct, req); err != nil {
				log.Errorf("Fronted request failed: %v", err)
			} else {
				log.Debug("Fronted request succeeded")
			}
		}
	}()
	go func() {
		if client, err := HTTPClient("", defaultAddr); err != nil {
			log.Errorf("Could not create HTTP client: %v", err)
			errs <- err
		} else {
			log.Debug("Sending chained request")
			if err := request(client, req); err != nil {
				log.Errorf("Chained request failed %v", err)
			} else {
				log.Debug("Switching to chained fronter for future requests since it succeeded")
				df.cf.fetcher = &chainedFetcher{}
			}
		}
	}()

	for i := 0; i < 2; i++ {
		select {
		case resp := <-responses:
			if i == 1 {
				log.Debugf("Got second response -- sending")
				return resp, nil
			} else if success(resp) {
				log.Debugf("Got good response")
				// Returning preemptively here means the second response
				// will not be closed properly. We need to ultimately
				// handle that.
				return resp, nil
			} else {
				log.Debugf("Got bad first response -- wait for second")
				_ = resp.Body.Close()
			}
		case err := <-errs:
			log.Debugf("Got an error: %v", err)
			if i == 1 {
				return nil, errors.New("All requests errored")
			}
		}
	}
	return nil, errors.New("Reached end")
}

// PersistentHTTPClient creates an http.Client that persists across requests.
// If rootCA is specified, the client will validate the server's certificate
// on TLS connections against that RootCA. If proxyAddr is specified, the client
// will proxy through the given http proxy.
func PersistentHTTPClient(rootCA string, proxyAddr string) (*http.Client, error) {
	return httpClient(rootCA, proxyAddr, true)
}

// HTTPClient creates an http.Client. If rootCA is specified, the client will
// validate the server's certificate on TLS connections against that RootCA. If
// proxyAddr is specified, the client will proxy through the given http proxy.
func HTTPClient(rootCA string, proxyAddr string) (*http.Client, error) {
	return httpClient(rootCA, proxyAddr, false)
}

// httpClient creates an http.Client. If rootCA is specified, the client will
// validate the server's certificate on TLS connections against that RootCA. If
// proxyAddr is specified, the client will proxy through the given http proxy.
func httpClient(rootCA string, proxyAddr string, persistent bool) (*http.Client, error) {

	log.Debugf("Creating new HTTPClient with proxy: %v", proxyAddr)

	tr := &http.Transport{
		Dial: (&net.Dialer{
			Timeout:   60 * time.Second,
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: 10 * time.Second,

		// This method is typically used for creating a one-off HTTP client
		// that we don't want to keep around for future calls, making
		// persistent connections a potential source of file descriptor
		// leaks. Note the name of this variable is misleading -- it would
		// be clearer to call it DisablePersistentConnections -- i.e. it has
		// nothing to do with TCP keep alives along the lines of the KeepAlive
		// variable in net.Dialer.
		DisableKeepAlives: !persistent,
	}

	if rootCA != "" {
		caCert, err := keyman.LoadCertificateFromPEMBytes([]byte(rootCA))
		if err != nil {
			return nil, fmt.Errorf("Unable to decode rootCA: %s", err)
		}
		tr.TLSClientConfig = &tls.Config{
			RootCAs: caCert.PoolContainingCert(),
		}
	}

	if proxyAddr != "" {
		host, _, err := net.SplitHostPort(proxyAddr)
		if err != nil {
			return nil, fmt.Errorf("Unable to split host and port for %v: %v", proxyAddr, err)
		}

		noHostSpecified := host == ""
		if noHostSpecified {
			// For addresses of the form ":8080", prepend the loopback IP
			host = "127.0.0.1"
			proxyAddr = host + proxyAddr
		}

		log.Debugf("Waiting for proxy server to came online...")
		// Waiting for proxy server to came online.
		if err := waitforserver.WaitForServer("tcp", proxyAddr, 60*time.Second); err != nil {
			// Instead of finishing here we just log the error and continue, the client
			// we are going to create will surely fail when used and return errors,
			// those errors should be handled by the code that depends on such client.
			log.Errorf("Proxy never came online at %v: %q", proxyAddr, err)
		}
		log.Debugf("Connected to proxy")

		tr.Proxy = func(req *http.Request) (*url.URL, error) {
			return url.Parse("http://" + proxyAddr)
		}
	} else {
		log.Errorf("Using direct http client with no proxyAddr")
	}
	return &http.Client{Transport: tr}, nil
}
<commit_msg>Make sure we close all responses closes #3312<commit_after>package util

import (
	"crypto/tls"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"time"

	"github.com/getlantern/fronted"
	"github.com/getlantern/golog"
	"github.com/getlantern/keyman"
	"github.com/getlantern/waitforserver"
)

const (
	defaultAddr = "127.0.0.1:8787"
)

var (
	log = golog.LoggerFor("flashlight.util")

	// This is for doing direct domain fronting if necessary. We store this as
	// an instance variable because it caches TLS session configs.
	direct = fronted.NewDirect()
)

// HTTPFetcher is a simple interface for types that are able to fetch data over HTTP.
type HTTPFetcher interface {
	Do(req *http.Request) (*http.Response, error)
}

func success(resp *http.Response) bool {
	return resp.StatusCode > 199 && resp.StatusCode < 400
}

// NewChainedAndFronted creates a new struct for accessing resources using chained
// and direct fronted servers in parallel.
func NewChainedAndFronted() *chainedAndFronted {
	cf := &chainedAndFronted{}
	cf.fetcher = &dualFetcher{cf}
	return cf
}

// ChainedAndFronted fetches HTTP data in parallel using both chained and fronted
// servers.
type chainedAndFronted struct {
	fetcher HTTPFetcher
}

// Do will attempt to execute the specified HTTP request using only a chained fetcher
func (cf *chainedAndFronted) Do(req *http.Request) (*http.Response, error) {
	resp, err := cf.fetcher.Do(req)
	if err != nil {
		// If there's an error, switch back to using the dual fetcher.
		cf.fetcher = &dualFetcher{cf}
	} else if !success(resp) {
		cf.fetcher = &dualFetcher{cf}
	}
	return resp, err
}

type chainedFetcher struct {
}

// Do will attempt to execute the specified HTTP request using only a chained fetcher
func (cf *chainedFetcher) Do(req *http.Request) (*http.Response, error) {
	log.Debugf("Using chained fronter")
	if client, err := HTTPClient("", defaultAddr); err != nil {
		log.Errorf("Could not create HTTP client: %v", err)
		return nil, err
	} else {
		return client.Do(req)
	}
}

type dualFetcher struct {
	cf *chainedAndFronted
}

// Do will attempt to execute the specified HTTP request using both
// chained and fronted servers, simply returning the first response to
// arrive. Callers MUST use the Lantern-Fronted-URL HTTP header to
// specify the fronted URL to use.
func (df *dualFetcher) Do(req *http.Request) (*http.Response, error) {
	log.Debugf("Using dual fronter")
	frontedUrl := req.Header.Get("Lantern-Fronted-URL")
	req.Header.Del("Lantern-Fronted-URL")

	if frontedUrl == "" {
		return nil, errors.New("Callers MUST specify the fronted URL in the Lantern-Fronted-URL header")
	}
	responses := make(chan *http.Response, 2)
	errs := make(chan error, 2)

	request := func(client HTTPFetcher, req *http.Request) error {
		if resp, err := client.Do(req); err != nil {
			log.Errorf("Could not complete request with: %v, %v", frontedUrl, err)
			errs <- err
			return err
		} else {
			if success(resp) {
				log.Debugf("Got successful HTTP call!")
				responses <- resp
				return nil
			} else {
				// If the local proxy can't connect to any upstread proxies, for example,
				// it will return a 502.
				err := fmt.Errorf("Bad response code: %v", resp.StatusCode)
				errs <- err
				return err
			}
		}
	}

	go func() {
		if req, err := http.NewRequest("GET", frontedUrl, nil); err != nil {
			log.Errorf("Could not create request for: %v, %v", frontedUrl, err)
			errs <- err
		} else {
			log.Debug("Sending request via DDF")
			if err := request(direct, req); err != nil {
				log.Errorf("Fronted request failed: %v", err)
			} else {
				log.Debug("Fronted request succeeded")
			}
		}
	}()
	go func() {
		if client, err := HTTPClient("", defaultAddr); err != nil {
			log.Errorf("Could not create HTTP client: %v", err)
			errs <- err
		} else {
			log.Debug("Sending chained request")
			if err := request(client, req); err != nil {
				log.Errorf("Chained request failed %v", err)
			} else {
				log.Debug("Switching to chained fronter for future requests since it succeeded")
				df.cf.fetcher = &chainedFetcher{}
			}
		}
	}()

	// Create channels for the final response or error. The response channel will be filled
	// in the case of any successful response as well as a non-error response for the second
	// response received. The error channel will only be filled if the first response non-successful
	// and the second is an error.
	finalResponseCh := make(chan *http.Response, 1)
	finalErrorCh := make(chan error, 1)

	go readResponses(finalResponseCh, responses, finalErrorCh, errs)

	for {
		select {
		case resp := <-finalResponseCh:
			return resp, nil
		case err := <-finalErrorCh:
			return nil, err
		}
	}
}

func readResponses(finalResponse chan *http.Response, responses chan *http.Response, finalErr chan error, errs chan error) {
	for i := 0; i < 2; i++ {
		select {
		case resp := <-responses:
			if i == 1 {
				log.Debug("Got second response -- sending")
				finalResponse <- resp
			} else if success(resp) {
				log.Debug("Got good response")
				finalResponse <- resp
				select {
				case <-responses:
					log.Debug("Closing second response body")
					_ = resp.Body.Close()
					return
				case <-errs:
					log.Debug("Ignoring error on second response")
					return
				}
			} else {
				log.Debugf("Got bad first response -- wait for second")
				// Note that the caller is responsible for closing the
				// response body of the response they receive.
				_ = resp.Body.Close()
			}
		case err := <-errs:
			log.Debugf("Got an error: %v", err)
			if i == 1 {
				// In this case all requests have errored, so our final response
				// is an error.
				finalErr <- err
			}
		}
	}
}

// PersistentHTTPClient creates an http.Client that persists across requests.
// If rootCA is specified, the client will validate the server's certificate
// on TLS connections against that RootCA. If proxyAddr is specified, the client
// will proxy through the given http proxy.
func PersistentHTTPClient(rootCA string, proxyAddr string) (*http.Client, error) {
	return httpClient(rootCA, proxyAddr, true)
}

// HTTPClient creates an http.Client. If rootCA is specified, the client will
// validate the server's certificate on TLS connections against that RootCA. If
// proxyAddr is specified, the client will proxy through the given http proxy.
func HTTPClient(rootCA string, proxyAddr string) (*http.Client, error) {
	return httpClient(rootCA, proxyAddr, false)
}

// httpClient creates an http.Client. If rootCA is specified, the client will
// validate the server's certificate on TLS connections against that RootCA. If
// proxyAddr is specified, the client will proxy through the given http proxy.
func httpClient(rootCA string, proxyAddr string, persistent bool) (*http.Client, error) {

	log.Debugf("Creating new HTTPClient with proxy: %v", proxyAddr)

	tr := &http.Transport{
		Dial: (&net.Dialer{
			Timeout:   60 * time.Second,
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: 10 * time.Second,

		// This method is typically used for creating a one-off HTTP client
		// that we don't want to keep around for future calls, making
		// persistent connections a potential source of file descriptor
		// leaks. Note the name of this variable is misleading -- it would
		// be clearer to call it DisablePersistentConnections -- i.e. it has
		// nothing to do with TCP keep alives along the lines of the KeepAlive
		// variable in net.Dialer.
		DisableKeepAlives: !persistent,
	}

	if rootCA != "" {
		caCert, err := keyman.LoadCertificateFromPEMBytes([]byte(rootCA))
		if err != nil {
			return nil, fmt.Errorf("Unable to decode rootCA: %s", err)
		}
		tr.TLSClientConfig = &tls.Config{
			RootCAs: caCert.PoolContainingCert(),
		}
	}

	if proxyAddr != "" {
		host, _, err := net.SplitHostPort(proxyAddr)
		if err != nil {
			return nil, fmt.Errorf("Unable to split host and port for %v: %v", proxyAddr, err)
		}

		noHostSpecified := host == ""
		if noHostSpecified {
			// For addresses of the form ":8080", prepend the loopback IP
			host = "127.0.0.1"
			proxyAddr = host + proxyAddr
		}

		log.Debugf("Waiting for proxy server to came online...")
		// Waiting for proxy server to came online.
		if err := waitforserver.WaitForServer("tcp", proxyAddr, 60*time.Second); err != nil {
			// Instead of finishing here we just log the error and continue, the client
			// we are going to create will surely fail when used and return errors,
			// those errors should be handled by the code that depends on such client.
			log.Errorf("Proxy never came online at %v: %q", proxyAddr, err)
		}
		log.Debugf("Connected to proxy")

		tr.Proxy = func(req *http.Request) (*url.URL, error) {
			return url.Parse("http://" + proxyAddr)
		}
	} else {
		log.Errorf("Using direct http client with no proxyAddr")
	}
	return &http.Client{Transport: tr}, nil
}
<|endoftext|>


<commit_before>// Package brew implements the Pipe, providing formula generation and
// uploading it to a configured repo.
package brew

import (
	"bytes"
	"errors"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/apex/log"
	"github.com/goreleaser/goreleaser/checksum"
	"github.com/goreleaser/goreleaser/config"
	"github.com/goreleaser/goreleaser/context"
	"github.com/goreleaser/goreleaser/internal/archiveformat"
	"github.com/goreleaser/goreleaser/internal/client"
)

// ErrNoDarwin64Build when there is no build for darwin_amd64 (goos doesn't
// contain darwin and/or goarch doesn't contain amd64)
var ErrNoDarwin64Build = errors.New("brew tap requires a darwin amd64 build")

const platform = "darwinamd64"

const formula = `class {{ .Name }} < Formula
  desc "{{ .Desc }}"
  homepage "{{ .Homepage }}"
  url "https://github.com/{{ .Repo.Owner }}/{{ .Repo.Name }}/releases/download/{{ .Tag }}/{{ .File }}"
  version "{{ .Version }}"
  sha256 "{{ .SHA256 }}"

  {{- if .Dependencies }}
  {{ range $index, $element := .Dependencies }}
  depends_on "{{ . }}"
  {{- end }}
  {{- end }}

  {{- if .Conflicts }}
  {{ range $index, $element := .Conflicts }}
  conflicts_with "{{ . }}"
  {{- end }}
  {{- end }}

  def install
    {{- range $index, $element := .Install }}
    {{ . -}}
    {{- end }}
  end

  {{- if .Caveats }}
  def caveats
    "{{ .Caveats }}"
  end
  {{- end }}

  {{- if .Plist }}
  def plist; <<-EOS.undent
    {{ .Plist }}
    EOS
  end
  {{- end }}

  {{- if .Test }}
  def test
  {{ .Test }}
  end
  {{- end }}
end
`

type templateData struct {
	Name         string
	Desc         string
	Homepage     string
	Repo         config.Repo // FIXME: will not work for anything but github right now.
	Tag          string
	Version      string
	Caveats      string
	File         string
	SHA256       string
	Plist        string
	Install      []string
	Dependencies []string
	Conflicts    []string
	Test         string
}

// Pipe for brew deployment
type Pipe struct{}

// Description of the pipe
func (Pipe) Description() string {
	return "Creating homebrew formula"
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	return doRun(ctx, client.NewGitHub(ctx))
}

func doRun(ctx *context.Context, client client.Client) error {
	if !ctx.Publish {
		log.Warn("skipped because --skip-publish is set")
		return nil
	}
	if ctx.Config.Brew.GitHub.Name == "" {
		log.Warn("skipped because brew section is not configured")
		return nil
	}
	if ctx.Config.Release.Draft {
		log.Warn("skipped because release is marked as draft")
		return nil
	}
	if ctx.Config.Archive.Format == "binary" {
		log.Info("skipped because archive format is binary")
		return nil
	}
	var group = ctx.Binaries["darwinamd64"]
	if group == nil {
		return ErrNoDarwin64Build
	}
	var folder string
	for f := range group {
		folder = f
		break
	}
	var path = filepath.Join(ctx.Config.Brew.Folder, ctx.Config.ProjectName+".rb")
	log.WithField("formula", path).
		WithField("repo", ctx.Config.Brew.GitHub.String()).
		Info("pushing")
	content, err := buildFormula(ctx, client, folder)
	if err != nil {
		return err
	}
	return client.CreateFile(ctx, content, path)
}

func buildFormula(ctx *context.Context, client client.Client, folder string) (bytes.Buffer, error) {
	data, err := dataFor(ctx, client, folder)
	if err != nil {
		return bytes.Buffer{}, err
	}
	return doBuildFormula(data)
}

func doBuildFormula(data templateData) (bytes.Buffer, error) {
	var out bytes.Buffer
	tmpl, err := template.New(data.Name).Parse(formula)
	if err != nil {
		return out, err
	}
	err = tmpl.Execute(&out, data)
	return out, err
}

func dataFor(ctx *context.Context, client client.Client, folder string) (result templateData, err error) {
	var file = folder + "." + archiveformat.For(ctx, platform)
	sum, err := checksum.SHA256(filepath.Join(ctx.Config.Dist, file))
	if err != nil {
		return
	}
	return templateData{
		Name:         formulaNameFor(ctx.Config.ProjectName),
		Desc:         ctx.Config.Brew.Description,
		Homepage:     ctx.Config.Brew.Homepage,
		Repo:         ctx.Config.Release.GitHub,
		Tag:          ctx.Git.CurrentTag,
		Version:      ctx.Version,
		Caveats:      ctx.Config.Brew.Caveats,
		File:         file,
		SHA256:       sum,
		Dependencies: ctx.Config.Brew.Dependencies,
		Conflicts:    ctx.Config.Brew.Conflicts,
		Plist:        ctx.Config.Brew.Plist,
		Test:         ctx.Config.Brew.Test,
		Install:      strings.Split(ctx.Config.Brew.Install, "\n"),
	}, err
}

func formulaNameFor(name string) string {
	name = strings.Replace(name, "-", " ", -1)
	name = strings.Replace(name, "_", " ", -1)
	return strings.Replace(strings.Title(name), " ", "", -1)
}
<commit_msg>template<commit_after>// Package brew implements the Pipe, providing formula generation and
// uploading it to a configured repo.
package brew

import (
	"bytes"
	"errors"
	"path/filepath"
	"strings"
	"text/template"

	"github.com/apex/log"
	"github.com/goreleaser/goreleaser/checksum"
	"github.com/goreleaser/goreleaser/config"
	"github.com/goreleaser/goreleaser/context"
	"github.com/goreleaser/goreleaser/internal/archiveformat"
	"github.com/goreleaser/goreleaser/internal/client"
)

// ErrNoDarwin64Build when there is no build for darwin_amd64 (goos doesn't
// contain darwin and/or goarch doesn't contain amd64)
var ErrNoDarwin64Build = errors.New("brew tap requires a darwin amd64 build")

const platform = "darwinamd64"

const formula = `class {{ .Name }} < Formula
  desc "{{ .Desc }}"
  homepage "{{ .Homepage }}"
  url "https://github.com/{{ .Repo.Owner }}/{{ .Repo.Name }}/releases/download/{{ .Tag }}/{{ .File }}"
  version "{{ .Version }}"
  sha256 "{{ .SHA256 }}"

  {{- if .Dependencies }}
  {{ range $index, $element := .Dependencies }}
  depends_on "{{ . }}"
  {{- end }}
  {{- end }}

  {{- if .Conflicts }}
  {{ range $index, $element := .Conflicts }}
  conflicts_with "{{ . }}"
  {{- end }}
  {{- end }}

  def install
    {{- range $index, $element := .Install }}
    {{ . -}}
    {{- end }}
  end

  {{- if .Caveats }}
  def caveats
    "{{ .Caveats }}"
  end
  {{- end }}

  {{- if .Plist }}
  def plist; <<-EOS.undent
    {{ .Plist }}
    EOS
  end
  {{- end }}

  {{- if .Test }}
  def test
  {{ .Test }}
  end
  {{- end }}
end
`

type templateData struct {
	Name         string
	Desc         string
	Homepage     string
	Repo         config.Repo // FIXME: will not work for anything but github right now.
	Tag          string
	Version      string
	Caveats      string
	File         string
	SHA256       string
	Plist        string
	Install      []string
	Dependencies []string
	Conflicts    []string
	Test         string
}

// Pipe for brew deployment
type Pipe struct{}

// Description of the pipe
func (Pipe) Description() string {
	return "Creating homebrew formula"
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	return doRun(ctx, client.NewGitHub(ctx))
}

func doRun(ctx *context.Context, client client.Client) error {
	if !ctx.Publish {
		log.Warn("skipped because --skip-publish is set")
		return nil
	}
	if ctx.Config.Brew.GitHub.Name == "" {
		log.Warn("skipped because brew section is not configured")
		return nil
	}
	if ctx.Config.Release.Draft {
		log.Warn("skipped because release is marked as draft")
		return nil
	}
	if ctx.Config.Archive.Format == "binary" {
		log.Info("skipped because archive format is binary")
		return nil
	}
	var group = ctx.Binaries["darwinamd64"]
	if group == nil {
		return ErrNoDarwin64Build
	}
	var folder string
	for f := range group {
		folder = f
		break
	}
	var path = filepath.Join(ctx.Config.Brew.Folder, ctx.Config.ProjectName+".rb")
	log.WithField("formula", path).
		WithField("repo", ctx.Config.Brew.GitHub.String()).
		Info("pushing")
	content, err := buildFormula(ctx, client, folder)
	if err != nil {
		return err
	}
	return client.CreateFile(ctx, content, path)
}

func buildFormula(ctx *context.Context, client client.Client, folder string) (bytes.Buffer, error) {
	data, err := dataFor(ctx, client, folder)
	if err != nil {
		return bytes.Buffer{}, err
	}
	return doBuildFormula(data)
}

func doBuildFormula(data templateData) (bytes.Buffer, error) {
	var out bytes.Buffer
	tmpl, err := template.New(data.Name).Parse(formula)
	if err != nil {
		return out, err
	}
	err = tmpl.Execute(&out, data)
	return out, err
}

func dataFor(ctx *context.Context, client client.Client, folder string) (result templateData, err error) {
	var file = folder + "." + archiveformat.For(ctx, platform)
	sum, err := checksum.SHA256(filepath.Join(ctx.Config.Dist, file))
	if err != nil {
		return
	}
	return templateData{
		Name:         formulaNameFor(ctx.Config.ProjectName),
		Desc:         ctx.Config.Brew.Description,
		Homepage:     ctx.Config.Brew.Homepage,
		Repo:         ctx.Config.Release.GitHub,
		Tag:          ctx.Git.CurrentTag,
		Version:      ctx.Version,
		Caveats:      ctx.Config.Brew.Caveats,
		File:         file,
		SHA256:       sum,
		Dependencies: ctx.Config.Brew.Dependencies,
		Conflicts:    ctx.Config.Brew.Conflicts,
		Plist:        ctx.Config.Brew.Plist,
		Test:         ctx.Config.Brew.Test,
		Install:      strings.Split(ctx.Config.Brew.Install, "\n"),
	}, err
}

func formulaNameFor(name string) string {
	name = strings.Replace(name, "-", " ", -1)
	name = strings.Replace(name, "_", " ", -1)
	return strings.Replace(strings.Title(name), " ", "", -1)
}
<|endoftext|>


<commit_before>package encryptor

import (
	"errors"
	"os"
	"time"

	"github.com/cloudfoundry-incubator/bbs/db"
	etcddb "github.com/cloudfoundry-incubator/bbs/db/etcd"
	"github.com/cloudfoundry-incubator/bbs/encryption"
	"github.com/cloudfoundry-incubator/bbs/format"
	"github.com/cloudfoundry-incubator/bbs/models"
	"github.com/cloudfoundry-incubator/runtime-schema/metric"
	"github.com/coreos/go-etcd/etcd"
	"github.com/pivotal-golang/clock"
	"github.com/pivotal-golang/lager"
)

const (
	encryptionDuration = metric.Duration("EncryptionDuration")
)

type Encryptor struct {
	logger      lager.Logger
	db          db.DB
	keyManager  encryption.KeyManager
	cryptor     encryption.Cryptor
	storeClient etcddb.StoreClient
	clock       clock.Clock
}

func New(
	logger lager.Logger,
	db db.DB,
	keyManager encryption.KeyManager,
	cryptor encryption.Cryptor,
	storeClient etcddb.StoreClient,
	clock clock.Clock,
) Encryptor {
	return Encryptor{
		logger:      logger,
		db:          db,
		keyManager:  keyManager,
		cryptor:     cryptor,
		storeClient: storeClient,
		clock:       clock,
	}
}

func (m Encryptor) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
	logger := m.logger.Session("encryptor")

	currentEncryptionKey, err := m.db.EncryptionKeyLabel(logger)
	if err != nil {
		if models.ConvertError(err) != models.ErrResourceNotFound {
			return err
		}
	} else {
		if m.keyManager.DecryptionKey(currentEncryptionKey) == nil {
			return errors.New("Existing encryption key version (" + currentEncryptionKey + ") is not among the known keys")
		}
	}

	close(ready)

	if currentEncryptionKey != m.keyManager.EncryptionKey().Label() {
		encryptionStart := m.clock.Now()
		logger.Debug("encryption-started")
		m.performEncryption(logger)
		logger.Debug("encryption-finished")
		m.db.SetEncryptionKeyLabel(logger, m.keyManager.EncryptionKey().Label())
		encryptionDuration.Send(time.Since(encryptionStart))
	}

	select {
	case <-signals:
		return nil
	}
}

func (m Encryptor) performEncryption(logger lager.Logger) error {
	response, err := m.storeClient.Get(etcddb.V1SchemaRoot, false, true)
	if err != nil {
		err = etcddb.ErrorFromEtcdError(logger, err)

		// Continue if the root node does not exist
		if err != models.ErrResourceNotFound {
			return err
		}
	}

	if response != nil {
		rootNode := response.Node
		return m.rewriteNode(logger, rootNode)
	}

	return nil
}

func (m Encryptor) rewriteNode(logger lager.Logger, node *etcd.Node) error {
	if !node.Dir {
		encoder := format.NewEncoder(m.cryptor)
		payload, err := encoder.Decode([]byte(node.Value))
		if err != nil {
			logger.Error("failed-to-read-node", err, lager.Data{"etcd-key": node.Key})
			return nil
		}
		encryptedPayload, err := encoder.Encode(format.BASE64_ENCRYPTED, payload)
		if err != nil {
			return err
		}
		_, err = m.storeClient.CompareAndSwap(node.Key, encryptedPayload, etcddb.NO_TTL, node.ModifiedIndex)
		if err != nil {
			logger.Info("failed-to-compare-and-swap", lager.Data{"err": err, "etcd-key": node.Key})
			return nil
		}
	} else {
		for _, child := range node.Nodes {
			err := m.rewriteNode(logger, child)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
<commit_msg>Encryptor uses clock object, not time package<commit_after>package encryptor

import (
	"errors"
	"os"

	"github.com/cloudfoundry-incubator/bbs/db"
	etcddb "github.com/cloudfoundry-incubator/bbs/db/etcd"
	"github.com/cloudfoundry-incubator/bbs/encryption"
	"github.com/cloudfoundry-incubator/bbs/format"
	"github.com/cloudfoundry-incubator/bbs/models"
	"github.com/cloudfoundry-incubator/runtime-schema/metric"
	"github.com/coreos/go-etcd/etcd"
	"github.com/pivotal-golang/clock"
	"github.com/pivotal-golang/lager"
)

const (
	encryptionDuration = metric.Duration("EncryptionDuration")
)

type Encryptor struct {
	logger      lager.Logger
	db          db.DB
	keyManager  encryption.KeyManager
	cryptor     encryption.Cryptor
	storeClient etcddb.StoreClient
	clock       clock.Clock
}

func New(
	logger lager.Logger,
	db db.DB,
	keyManager encryption.KeyManager,
	cryptor encryption.Cryptor,
	storeClient etcddb.StoreClient,
	clock clock.Clock,
) Encryptor {
	return Encryptor{
		logger:      logger,
		db:          db,
		keyManager:  keyManager,
		cryptor:     cryptor,
		storeClient: storeClient,
		clock:       clock,
	}
}

func (m Encryptor) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
	logger := m.logger.Session("encryptor")

	currentEncryptionKey, err := m.db.EncryptionKeyLabel(logger)
	if err != nil {
		if models.ConvertError(err) != models.ErrResourceNotFound {
			return err
		}
	} else {
		if m.keyManager.DecryptionKey(currentEncryptionKey) == nil {
			return errors.New("Existing encryption key version (" + currentEncryptionKey + ") is not among the known keys")
		}
	}

	close(ready)

	if currentEncryptionKey != m.keyManager.EncryptionKey().Label() {
		encryptionStart := m.clock.Now()
		logger.Debug("encryption-started")
		m.performEncryption(logger)
		m.db.SetEncryptionKeyLabel(logger, m.keyManager.EncryptionKey().Label())
		totalTime := m.clock.Since(encryptionStart)
		logger.Debug("encryption-finished", lager.Data{"total-time": totalTime})
		encryptionDuration.Send(totalTime)
	}

	select {
	case <-signals:
		return nil
	}
}

func (m Encryptor) performEncryption(logger lager.Logger) error {
	response, err := m.storeClient.Get(etcddb.V1SchemaRoot, false, true)
	if err != nil {
		err = etcddb.ErrorFromEtcdError(logger, err)

		// Continue if the root node does not exist
		if err != models.ErrResourceNotFound {
			return err
		}
	}

	if response != nil {
		rootNode := response.Node
		return m.rewriteNode(logger, rootNode)
	}

	return nil
}

func (m Encryptor) rewriteNode(logger lager.Logger, node *etcd.Node) error {
	if !node.Dir {
		encoder := format.NewEncoder(m.cryptor)
		payload, err := encoder.Decode([]byte(node.Value))
		if err != nil {
			logger.Error("failed-to-read-node", err, lager.Data{"etcd-key": node.Key})
			return nil
		}
		encryptedPayload, err := encoder.Encode(format.BASE64_ENCRYPTED, payload)
		if err != nil {
			return err
		}
		_, err = m.storeClient.CompareAndSwap(node.Key, encryptedPayload, etcddb.NO_TTL, node.ModifiedIndex)
		if err != nil {
			logger.Info("failed-to-compare-and-swap", lager.Data{"err": err, "etcd-key": node.Key})
			return nil
		}
	} else {
		for _, child := range node.Nodes {
			err := m.rewriteNode(logger, child)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
<|endoftext|>