Skip to content

Commit

Permalink
inputs: implement go source file discovery for inputs
Browse files Browse the repository at this point in the history
Add a new application config directive "Build.Input.GolangSources".
It accept a list of directories containing golang source files,
evaluates the imports recursively and uses the found source files as
inputs.
Testfiles and files belonging to the stdlib are ignored.

Because we copied GPL-2 code from https://github.com/rogpeppe/showdeps,
we have to make available the (modified?) code also with a GPL2
compatible license.
  • Loading branch information
fho committed Aug 8, 2018
1 parent eed190a commit 7b5e722
Show file tree
Hide file tree
Showing 28 changed files with 3,747 additions and 6 deletions.
17 changes: 16 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@
[[constraint]]
branch = "master"
name = "github.com/lib/pq"

[[constraint]]
branch = "master"
name = "github.com/rogpeppe/godeps"
339 changes: 339 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ baur will implement:
- detect if build artifacts for an application version already exist or if it's
need to be build

Baur makes certain Assumptions:
baur makes certain Assumptions:
- the baur repository is part of a git repository,
- an application directory only contains one application,
- an application can be build by running a single command,
Expand All @@ -25,7 +25,7 @@ Baur makes certain Assumptions:
To build baur run `make`.

## Dependencies
- The git command lines tools are used to retrieve informations in a baur
- The git command lines tools are used to retrieve information in a baur
repository. The tools must be installed and be in one of the paths of the
`$PATH` environment variable.

Expand Down Expand Up @@ -95,6 +95,20 @@ The docker image is specified by it's manifest digest.
The manifest digest for a docker image can be retrieved with
`docker images --digests` or `docker inspect`

#### `[Build.Input.GolangSources]`
Allows to add Golang applications as inputs.
The `paths` parameters take a list of paths to directories relative to the
application directory.
In every directory `.go` files are discovered and the files they depend on.
Imports in the `.go` files are evaluated, resolved to files and tracked as build
inputs.
Go test files and imports belong to the Golang stdlib are ignored.

To be able to resolve the imports either the `GOPATH` environment variable must
be set correctly or alternatively the `go_path` parameter in the config section
must be set the `GOPATH`. The `go_path` expects a path relative to the
application directory.

### Build Outputs
Build outputs are the results that are produced by a build. They can be
described in the `[Build.Output]` section.
Expand Down
16 changes: 16 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,15 @@ func replaceGitCommitVar(in string, r *Repository) (string, error) {

func (a *App) setInputsFromCfg(r *Repository, cfg *cfg.App) error {
sliceLen := len(cfg.Build.Input.Files.Paths) + len(cfg.Build.Input.DockerImage)

if len(cfg.Build.Input.GitFiles.Paths) > 0 {
sliceLen++
}

if len(cfg.Build.Input.GolangSources.Paths) > 0 {
sliceLen++
}

a.BuildInputPaths = make([]BuildInputPathResolver, 0, sliceLen)

for _, p := range cfg.Build.Input.Files.Paths {
Expand All @@ -65,6 +70,17 @@ func (a *App) setInputsFromCfg(r *Repository, cfg *cfg.App) error {
a.BuildInputPaths = append(a.BuildInputPaths, &DockerImageRef{Repository: d.Repository, Digest: d.Digest})
}

if len(cfg.Build.Input.GolangSources.Paths) > 0 {
var gopath string

if len(cfg.Build.Input.GolangSources.GoPath) > 0 {
gopath = filepath.Join(a.Path, cfg.Build.Input.GolangSources.GoPath)
}

a.BuildInputPaths = append(a.BuildInputPaths,
NewGoSrcDirs(r.Path, a.RelPath, gopath, cfg.Build.Input.GolangSources.Paths))
}

return nil
}

Expand Down
32 changes: 29 additions & 3 deletions cfg/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,16 @@ type Build struct {
// BuildInput contains information about inputs (sources, compiler, docker
// images) for an build
type BuildInput struct {
Files FileInputs `comment:"file paths, e.g: source files, the used compiler binary "`
GitFiles GitFileInputs `comment:"If the baur repository is part of a git repository, this option can be used to specify source files tracked by git."`
DockerImage []*DockerImageInput `comment:"docker images that are used to build the application or affect in other ways the produces artifact"`
Files FileInputs `comment:"file paths, e.g: source files, the used compiler binary "`
GitFiles GitFileInputs `comment:"If the baur repository is part of a git repository, this option can be used to specify source files tracked by git."`
GolangSources GolangSources `comment:"Directories containing Golang applications, all source files to build the application are located and added as inputs (excluding stdlib and test files)"`
DockerImage []*DockerImageInput `comment:"docker images that are used to build the application or affect in other ways the produces artifact"`
}

// GolangSources specifies inputs for Golang Applications
type GolangSources struct {
Paths []string `toml:"paths" comment:"paths to directories containing Golang source files" commented:"true"`
GoPath string `toml:"go_path" comment:"specifies the GOPATH, that is used for source file discovery, if not set or empty the current GOPATH is used. The go_path is relative to the application directory." commented:"true"`
}

// DockerImageInput specifies a docker image as build source
Expand Down Expand Up @@ -100,6 +107,10 @@ func ExampleApp(name string) *App {
Digest: "sha256:b1589cc882898e1e726994bbf9827953156b94d423dae8c89b56614ec298684e",
},
},
GolangSources: GolangSources{
Paths: []string{"."},
GoPath: "../",
},
},
Output: BuildOutput{
File: []*FileOutput{
Expand Down Expand Up @@ -236,6 +247,10 @@ func (b *BuildInput) Validate() error {
return errors.Wrap(err, "[Build.Input.Files] section contains errors")
}

if err := b.GolangSources.Validate(); err != nil {
return errors.Wrap(err, "[Build.Input.Files] section contains errors")
}

// TODO: add validation for gitfiles section

for _, d := range b.DockerImage {
Expand All @@ -247,6 +262,17 @@ func (b *BuildInput) Validate() error {
return nil
}

// Validate validates the GolangSources section
func (g *GolangSources) Validate() error {
for _, p := range g.Paths {
if len(p) == 0 {
return errors.New("a path can not be empty")
}
}

return nil
}

// Validate validates the BuildOutput section
func (b *BuildOutput) Validate() error {
for _, f := range b.File {
Expand Down
140 changes: 140 additions & 0 deletions golang/golang/golang.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Package golang determines all Go Source files that are imported by a Go-Files
// in a directory.
// Most of the code is based on a slightly modified version of https://github.com/rogpeppe/showdeps
package golang

import (
"os"
"path/filepath"
"strings"

"github.com/kisielk/gotool"
"github.com/pkg/errors"
"github.com/rogpeppe/godeps/build"
)

// SrcFiles returns the Go source files in the passed directories plus all
// source files of the imported packages.
// Testfiles and stdlib dependencies are ignored.
func SrcFiles(gopath string, dirs ...string) ([]string, error) {
var allFiles []string
ctx := build.Default

if len(gopath) > 0 {
ctx.GOPATH = gopath
}

for _, d := range dirs {
files, err := resolve(ctx, d)
if err != nil {
return nil, err
}

allFiles = append(allFiles, files...)
}

return allFiles, nil
}

func resolve(ctx build.Context, path string) ([]string, error) {
recur := true
pkgs := []string{"./..."}

if err := os.Chdir(path); err != nil {
return nil, errors.Wrapf(err, "changing cwd to %q failed", path)
}

pkgs = gotool.ImportPaths(pkgs)

rootPkgs := make(map[string]bool)
for _, pkg := range pkgs {
p, err := ctx.Import(pkg, path, build.FindOnly)
if err != nil {
return nil, errors.Wrapf(err, "cannot find %q", pkg)
}

rootPkgs[p.ImportPath] = true
}

allPkgs := make(map[string][]string)
for pkg := range rootPkgs {
if err := findImports(ctx, pkg, path, recur, allPkgs, rootPkgs); err != nil {
return nil, errors.Wrapf(err, "cannot find imports from %q", pkg)
}
}

files := make([]string, 0, len(allPkgs))
for pkgName := range allPkgs {
pkg, err := ctx.Import(pkgName, path, 0)
if err != nil {
return nil, errors.Wrapf(err, "determining imports from %q failed", pkg)
}

gofiles := absFilePaths(pkg, pkg.GoFiles)
cgofiles := absFilePaths(pkg, pkg.CgoFiles)

files = append(files, gofiles...)
files = append(files, cgofiles...)
}

return files, nil
}

func absFilePaths(pkg *build.Package, fs []string) []string {
res := make([]string, 0, len(fs))

for _, f := range fs {
res = append(res, filepath.Join(pkg.Dir, f))
}

return res
}

func isStdlib(pkg string) bool {
return !strings.Contains(strings.SplitN(pkg, "/", 2)[0], ".")
}

// findImports recursively adds all imported packages by the given
// package (packageName) to the allPkgs map.
func findImports(ctx build.Context, packageName, dir string, recur bool, allPkgs map[string][]string, rootPkgs map[string]bool) error {
if packageName == "C" {
return nil
}

pkg, err := ctx.Import(packageName, dir, 0)
if err != nil {
return errors.Wrapf(err, "cannot find %q", packageName)
}

// Iterate through the imports in sorted order so that we provide
// deterministic results.
for _, name := range imports(pkg, rootPkgs[pkg.ImportPath]) {
if isStdlib(name) {
continue
}

_, alreadyDone := allPkgs[name]
allPkgs[name] = append(allPkgs[name], pkg.ImportPath)
if recur && !alreadyDone {
if err := findImports(ctx, name, pkg.Dir, recur, allPkgs, rootPkgs); err != nil {
return err
}
}
}

return nil
}

func imports(pkg *build.Package, isRoot bool) []string {
var res []string

for _, s := range pkg.Imports {
if isStdlib(s) {
continue
}

res = append(res, s)
}

return res
}
Loading

0 comments on commit 7b5e722

Please sign in to comment.