Skip to content
This repository has been archived by the owner on Sep 9, 2020. It is now read-only.

Commit

Permalink
dep: add prune options to manifests
Browse files Browse the repository at this point in the history
Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
  • Loading branch information
ibrasho committed Oct 27, 2017
1 parent b260d7c commit 505c236
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 29 deletions.
5 changes: 5 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@
[[constraint]]
name = "github.com/golang/protobuf"
branch = "master"

[prune]
non-go = true
go-tests = true
unused-packages = true
2 changes: 1 addition & 1 deletion analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestAnalyzerDeriveManifestAndLock(t *testing.T) {
t.Fatal(err)
}
} else {
t.Fatalf("expected %s\n got %s", want, string(got))
t.Fatalf("(WNT):\n%s\n(GOT):\n%s", want, string(got))
}
}

Expand Down
5 changes: 4 additions & 1 deletion internal/gps/prune.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ import (
// PruneOptions represents the pruning options used to write the dependecy tree.
type PruneOptions uint8

// PruneProjectOptions is map of prune options per project name.
type PruneProjectOptions map[ProjectRoot]PruneOptions

const (
// PruneNestedVendorDirs indicates if nested vendor directories should be pruned.
PruneNestedVendorDirs = 1 << iota
PruneNestedVendorDirs PruneOptions = 1 << iota
// PruneUnusedPackages indicates if unused Go packages should be pruned.
PruneUnusedPackages
// PruneNonGoFiles indicates if non-Go files should be pruned.
Expand Down
209 changes: 184 additions & 25 deletions manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,39 @@ const ManifestName = "Gopkg.toml"

// Errors
var (
errInvalidConstraint = errors.New("\"constraint\" must be a TOML array of tables")
errInvalidOverride = errors.New("\"override\" must be a TOML array of tables")
errInvalidRequired = errors.New("\"required\" must be a TOML list of strings")
errInvalidIgnored = errors.New("\"ignored\" must be a TOML list of strings")
errInvalidProjectRoot = errors.New("ProjectRoot name validation failed")
errInvalidConstraint = errors.Errorf("%q must be a TOML array of tables", "constraint")
errInvalidOverride = errors.Errorf("%q must be a TOML array of tables", "override")
errInvalidRequired = errors.Errorf("%q must be a TOML list of strings", "required")
errInvalidIgnored = errors.Errorf("%q must be a TOML list of strings", "ignored")
errInvalidPrune = errors.Errorf("%q must be a TOML table of booleans", "prune")

errInvalidProjectRoot = errors.New("ProjectRoot name validation failed")
errInvalidPruneValue = errors.New("prune options values must be booleans")
errInvalidPruneProject = errors.Errorf("%q must be a TOML array of tables", "prune.project")
errPruneSubProject = errors.New("prune projects should not contain sub projects")

errInvalidRootPruneValue = errors.New("root prune options must be omitted instead of being set to false")
errInvalidPruneProjectName = errors.Errorf("%q in %q must be a string", "name", "prune.project")
)

// Manifest holds manifest file data and implements gps.RootManifest.
type Manifest struct {
Constraints gps.ProjectConstraints
Ovr gps.ProjectConstraints
Ignored []string
Required []string

Ignored []string
Required []string

PruneOptions gps.PruneOptions
PruneProjectOptions gps.PruneProjectOptions
}

type rawManifest struct {
Constraints []rawProject `toml:"constraint,omitempty"`
Overrides []rawProject `toml:"override,omitempty"`
Ignored []string `toml:"ignored,omitempty"`
Required []string `toml:"required,omitempty"`
Constraints []rawProject `toml:"constraint,omitempty"`
Overrides []rawProject `toml:"override,omitempty"`
Ignored []string `toml:"ignored,omitempty"`
Required []string `toml:"required,omitempty"`
PruneOptions rawPruneOptions `toml:"prune,omitempty"`
}

type rawProject struct {
Expand All @@ -54,11 +67,33 @@ type rawProject struct {
Source string `toml:"source,omitempty"`
}

// NewManifest instantiates a new manifest.
type rawPruneOptions struct {
UnusedPackages bool `toml:"unused-packages,omitempty"`
NonGoFiles bool `toml:"non-go,omitempty"`
GoTests bool `toml:"go-tests,omitempty"`

Projects []rawPruneProjectOptions `toml:"project,omitempty"`
}

type rawPruneProjectOptions struct {
Name string `toml:"name"`
UnusedPackages bool `toml:"unused-packages,omitempty"`
NonGoFiles bool `toml:"non-go,omitempty"`
GoTests bool `toml:"go-tests,omitempty"`
}

const (
pruneOptionUnusedPackages = "unused-packages"
pruneOptionGoTests = "go-tests"
pruneOptionNonGo = "non-go"
)

// NewManifest instantites a new manifest.
func NewManifest() *Manifest {
return &Manifest{
Constraints: make(gps.ProjectConstraints),
Ovr: make(gps.ProjectConstraints),
Constraints: make(gps.ProjectConstraints),
Ovr: make(gps.ProjectConstraints),
PruneOptions: gps.PruneNestedVendorDirs,
}
}

Expand Down Expand Up @@ -151,6 +186,12 @@ func validateManifest(s string) ([]error, error) {
return warns, errInvalidRequired
}
}
case "prune":
pruneWarns, err := validatePruneOptions(val, true)
warns = append(warns, pruneWarns...)
if err != nil {
return warns, err
}
default:
warns = append(warns, fmt.Errorf("unknown field in manifest: %v", prop))
}
Expand All @@ -159,6 +200,70 @@ func validateManifest(s string) ([]error, error) {
return warns, nil
}

func validatePruneOptions(val interface{}, root bool) (warns []error, err error) {
if reflect.TypeOf(val).Kind() != reflect.Map {
return warns, errInvalidPrune
}

for key, value := range val.(map[string]interface{}) {
switch key {
case pruneOptionNonGo, pruneOptionGoTests, pruneOptionUnusedPackages:
if option, ok := value.(bool); !ok {
return warns, errInvalidPruneValue
} else if root && !option {
return warns, errInvalidRootPruneValue
}
case "name":
if root {
warns = append(warns, errors.Errorf("%q should not include a name", "prune"))
} else if _, ok := value.(string); !ok {
return warns, errInvalidPruneProjectName
}
case "project":
if !root {
return warns, errPruneSubProject
}
if reflect.TypeOf(value).Kind() != reflect.Slice {
return warns, errInvalidPruneProject
}
for _, project := range value.([]interface{}) {
projectWarns, err := validatePruneOptions(project, false)
warns = append(warns, projectWarns...)
if err != nil {
return nil, err
}
}

default:
if root {
warns = append(warns, errors.Errorf("unknown field %q in %q", key, "prune"))
} else {
warns = append(warns, errors.Errorf("unknown field %q in %q", key, "prune.project"))
}
}
}

return warns, err
}

func checkRedundantPruneOptions(raw rawManifest) (warns []error) {
rootOptions := raw.PruneOptions

for _, project := range raw.PruneOptions.Projects {
if rootOptions.GoTests && project.GoTests {
warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionGoTests, project.Name))
}
if rootOptions.NonGoFiles && project.NonGoFiles {
warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionNonGo, project.Name))
}
if rootOptions.UnusedPackages && project.UnusedPackages {
warns = append(warns, errors.Errorf("redundant prune option %q set for %q", pruneOptionUnusedPackages, project.Name))
}
}

return warns
}

// ValidateProjectRoots validates the project roots present in manifest.
func ValidateProjectRoots(c *Ctx, m *Manifest, sm gps.SourceManager) error {
// Channel to receive all the errors
Expand All @@ -184,6 +289,10 @@ func ValidateProjectRoots(c *Ctx, m *Manifest, sm gps.SourceManager) error {
wg.Add(1)
go validate(pr)
}
for pr := range m.PruneProjectOptions {
wg.Add(1)
go validate(pr)
}

wg.Wait()
close(errorCh)
Expand Down Expand Up @@ -220,6 +329,8 @@ func readManifest(r io.Reader) (*Manifest, []error, error) {
return nil, warns, errors.Wrap(err, "unable to parse the manifest as TOML")
}

warns = append(warns, checkRedundantPruneOptions(raw)...)

m, err := fromRawManifest(raw)
return m, warns, err
}
Expand Down Expand Up @@ -254,9 +365,43 @@ func fromRawManifest(raw rawManifest) (*Manifest, error) {
m.Ovr[name] = prj
}

m.PruneOptions, m.PruneProjectOptions = fromRawPruneOptions(raw.PruneOptions)

return m, nil
}

func fromRawPruneOptions(raw rawPruneOptions) (gps.PruneOptions, gps.PruneProjectOptions) {
rootOptions := gps.PruneNestedVendorDirs
pruneProjects := make(gps.PruneProjectOptions)

if raw.UnusedPackages {
rootOptions |= gps.PruneUnusedPackages
}
if raw.GoTests {
rootOptions |= gps.PruneGoTestFiles
}
if raw.NonGoFiles {
rootOptions |= gps.PruneNonGoFiles
}

for _, p := range raw.Projects {
pr := gps.ProjectRoot(p.Name)
pruneProjects[pr] = gps.PruneNestedVendorDirs

if raw.UnusedPackages {
pruneProjects[pr] |= gps.PruneUnusedPackages
}
if raw.GoTests {
pruneProjects[pr] |= gps.PruneGoTestFiles
}
if raw.NonGoFiles {
pruneProjects[pr] |= gps.PruneNonGoFiles
}
}

return rootOptions, pruneProjects
}

// toProject interprets the string representations of project information held in
// a rawProject, converting them into a proper gps.ProjectProperties. An
// error is returned if the rawProject contains some invalid combination -
Expand Down Expand Up @@ -288,17 +433,27 @@ func toProject(raw rawProject) (n gps.ProjectRoot, pp gps.ProjectProperties, err
}

pp.Source = raw.Source

return n, pp, nil
}

// MarshalTOML serializes this manifest into TOML via an intermediate raw form.
func (m *Manifest) MarshalTOML() ([]byte, error) {
raw := m.toRaw()
result, err := toml.Marshal(raw)
return result, errors.Wrap(err, "unable to marshal the lock to a TOML string")
}

// toRaw converts the manifest into a representation suitable to write to the manifest file
func (m *Manifest) toRaw() rawManifest {
raw := rawManifest{
Constraints: make([]rawProject, 0, len(m.Constraints)),
Overrides: make([]rawProject, 0, len(m.Ovr)),
Ignored: m.Ignored,
Required: m.Required,
Constraints: make([]rawProject, 0, len(m.Constraints)),
Overrides: make([]rawProject, 0, len(m.Ovr)),
Ignored: m.Ignored,
Required: m.Required,
PruneOptions: rawPruneOptions{},
}

for n, prj := range m.Constraints {
raw.Constraints = append(raw.Constraints, toRawProject(n, prj))
}
Expand All @@ -309,6 +464,8 @@ func (m *Manifest) toRaw() rawManifest {
}
sort.Sort(sortedRawProjects(raw.Overrides))

// TODO(ibrasho): write out prune options.

return raw
}

Expand All @@ -329,13 +486,6 @@ func (s sortedRawProjects) Less(i, j int) bool {
return l.Source < r.Source
}

// MarshalTOML serializes this manifest into TOML via an intermediate raw form.
func (m *Manifest) MarshalTOML() ([]byte, error) {
raw := m.toRaw()
result, err := toml.Marshal(raw)
return result, errors.Wrap(err, "Unable to marshal the lock to a TOML string")
}

func toRawProject(name gps.ProjectRoot, project gps.ProjectProperties) rawProject {
raw := rawProject{
Name: string(name),
Expand Down Expand Up @@ -363,6 +513,7 @@ func toRawProject(name gps.ProjectRoot, project gps.ProjectProperties) rawProjec
// Has to be a semver range.
raw.Version = project.Constraint.ImpliedCaretString()
}

return raw
}

Expand Down Expand Up @@ -407,3 +558,11 @@ func (m *Manifest) RequiredPackages() map[string]bool {

return mp
}

func (m *Manifest) PruneOptionsFor(pr gps.ProjectRoot) gps.PruneOptions {
if po, ok := m.PruneProjectOptions[pr]; ok {
return po
}

return m.PruneOptions
}
Loading

0 comments on commit 505c236

Please sign in to comment.