dep: add prune options to manifests

Signed-off-by: Ibrahim AshShohail <ibra.sho@gmail.com>
This commit is contained in:
Ibrahim AshShohail 2017-09-29 11:49:51 +03:00
Родитель 4724b1f005
Коммит 13aa6fcff1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: F9188F2C77217B32
5 изменённых файлов: 231 добавлений и 29 удалений

Просмотреть файл

@ -30,3 +30,8 @@
[[constraint]] [[constraint]]
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
branch = "master" branch = "master"
[prune]
non-go = true
go-tests = true
unused-packages = true

Просмотреть файл

@ -38,7 +38,7 @@ func TestAnalyzerDeriveManifestAndLock(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} else { } else {
t.Fatalf("expected %s\n got %s", want, string(got)) t.Fatalf("(WNT):\n%s\n(GOT):\n%s", want, string(got))
} }
} }

Просмотреть файл

@ -16,9 +16,12 @@ import (
// PruneOptions represents the pruning options used to write the dependecy tree. // PruneOptions represents the pruning options used to write the dependecy tree.
type PruneOptions uint8 type PruneOptions uint8
// PruneProjectOptions is map of prune options per project name.
type PruneProjectOptions map[ProjectRoot]PruneOptions
const ( const (
// PruneNestedVendorDirs indicates if nested vendor directories should be pruned. // 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 indicates if unused Go packages should be pruned.
PruneUnusedPackages PruneUnusedPackages
// PruneNonGoFiles indicates if non-Go files should be pruned. // PruneNonGoFiles indicates if non-Go files should be pruned.

Просмотреть файл

@ -24,26 +24,39 @@ const ManifestName = "Gopkg.toml"
// Errors // Errors
var ( var (
errInvalidConstraint = errors.New("\"constraint\" must be a TOML array of tables") errInvalidConstraint = errors.Errorf("%q must be a TOML array of tables", "constraint")
errInvalidOverride = errors.New("\"override\" must be a TOML array of tables") errInvalidOverride = errors.Errorf("%q must be a TOML array of tables", "override")
errInvalidRequired = errors.New("\"required\" must be a TOML list of strings") errInvalidRequired = errors.Errorf("%q must be a TOML list of strings", "required")
errInvalidIgnored = errors.New("\"ignored\" must be a TOML list of strings") errInvalidIgnored = errors.Errorf("%q must be a TOML list of strings", "ignored")
errInvalidProjectRoot = errors.New("ProjectRoot name validation failed") 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. // Manifest holds manifest file data and implements gps.RootManifest.
type Manifest struct { type Manifest struct {
Constraints gps.ProjectConstraints Constraints gps.ProjectConstraints
Ovr gps.ProjectConstraints Ovr gps.ProjectConstraints
Ignored []string
Required []string Ignored []string
Required []string
PruneOptions gps.PruneOptions
PruneProjectOptions gps.PruneProjectOptions
} }
type rawManifest struct { type rawManifest struct {
Constraints []rawProject `toml:"constraint,omitempty"` Constraints []rawProject `toml:"constraint,omitempty"`
Overrides []rawProject `toml:"override,omitempty"` Overrides []rawProject `toml:"override,omitempty"`
Ignored []string `toml:"ignored,omitempty"` Ignored []string `toml:"ignored,omitempty"`
Required []string `toml:"required,omitempty"` Required []string `toml:"required,omitempty"`
PruneOptions rawPruneOptions `toml:"prune,omitempty"`
} }
type rawProject struct { type rawProject struct {
@ -54,11 +67,33 @@ type rawProject struct {
Source string `toml:"source,omitempty"` 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 { func NewManifest() *Manifest {
return &Manifest{ return &Manifest{
Constraints: make(gps.ProjectConstraints), Constraints: make(gps.ProjectConstraints),
Ovr: make(gps.ProjectConstraints), Ovr: make(gps.ProjectConstraints),
PruneOptions: gps.PruneNestedVendorDirs,
} }
} }
@ -151,6 +186,12 @@ func validateManifest(s string) ([]error, error) {
return warns, errInvalidRequired return warns, errInvalidRequired
} }
} }
case "prune":
pruneWarns, err := validatePruneOptions(val, true)
warns = append(warns, pruneWarns...)
if err != nil {
return warns, err
}
default: default:
warns = append(warns, fmt.Errorf("unknown field in manifest: %v", prop)) warns = append(warns, fmt.Errorf("unknown field in manifest: %v", prop))
} }
@ -159,6 +200,70 @@ func validateManifest(s string) ([]error, error) {
return warns, nil 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. // ValidateProjectRoots validates the project roots present in manifest.
func ValidateProjectRoots(c *Ctx, m *Manifest, sm gps.SourceManager) error { func ValidateProjectRoots(c *Ctx, m *Manifest, sm gps.SourceManager) error {
// Channel to receive all the errors // Channel to receive all the errors
@ -184,6 +289,10 @@ func ValidateProjectRoots(c *Ctx, m *Manifest, sm gps.SourceManager) error {
wg.Add(1) wg.Add(1)
go validate(pr) go validate(pr)
} }
for pr := range m.PruneProjectOptions {
wg.Add(1)
go validate(pr)
}
wg.Wait() wg.Wait()
close(errorCh) close(errorCh)
@ -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") return nil, warns, errors.Wrap(err, "unable to parse the manifest as TOML")
} }
warns = append(warns, checkRedundantPruneOptions(raw)...)
m, err := fromRawManifest(raw) m, err := fromRawManifest(raw)
return m, warns, err return m, warns, err
} }
@ -254,9 +365,43 @@ func fromRawManifest(raw rawManifest) (*Manifest, error) {
m.Ovr[name] = prj m.Ovr[name] = prj
} }
m.PruneOptions, m.PruneProjectOptions = fromRawPruneOptions(raw.PruneOptions)
return m, nil 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 // toProject interprets the string representations of project information held in
// a rawProject, converting them into a proper gps.ProjectProperties. An // a rawProject, converting them into a proper gps.ProjectProperties. An
// error is returned if the rawProject contains some invalid combination - // error is returned if the rawProject contains some invalid combination -
@ -288,17 +433,27 @@ func toProject(raw rawProject) (n gps.ProjectRoot, pp gps.ProjectProperties, err
} }
pp.Source = raw.Source pp.Source = raw.Source
return n, pp, nil 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 // toRaw converts the manifest into a representation suitable to write to the manifest file
func (m *Manifest) toRaw() rawManifest { func (m *Manifest) toRaw() rawManifest {
raw := rawManifest{ raw := rawManifest{
Constraints: make([]rawProject, 0, len(m.Constraints)), Constraints: make([]rawProject, 0, len(m.Constraints)),
Overrides: make([]rawProject, 0, len(m.Ovr)), Overrides: make([]rawProject, 0, len(m.Ovr)),
Ignored: m.Ignored, Ignored: m.Ignored,
Required: m.Required, Required: m.Required,
PruneOptions: rawPruneOptions{},
} }
for n, prj := range m.Constraints { for n, prj := range m.Constraints {
raw.Constraints = append(raw.Constraints, toRawProject(n, prj)) raw.Constraints = append(raw.Constraints, toRawProject(n, prj))
} }
@ -309,6 +464,8 @@ func (m *Manifest) toRaw() rawManifest {
} }
sort.Sort(sortedRawProjects(raw.Overrides)) sort.Sort(sortedRawProjects(raw.Overrides))
// TODO(ibrasho): write out prune options.
return raw return raw
} }
@ -329,13 +486,6 @@ func (s sortedRawProjects) Less(i, j int) bool {
return l.Source < r.Source 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 { func toRawProject(name gps.ProjectRoot, project gps.ProjectProperties) rawProject {
raw := rawProject{ raw := rawProject{
Name: string(name), Name: string(name),
@ -363,6 +513,7 @@ func toRawProject(name gps.ProjectRoot, project gps.ProjectProperties) rawProjec
// Has to be a semver range. // Has to be a semver range.
raw.Version = project.Constraint.ImpliedCaretString() raw.Version = project.Constraint.ImpliedCaretString()
} }
return raw return raw
} }
@ -407,3 +558,11 @@ func (m *Manifest) RequiredPackages() map[string]bool {
return mp return mp
} }
func (m *Manifest) PruneOptionsFor(pr gps.ProjectRoot) gps.PruneOptions {
if po, ok := m.PruneProjectOptions[pr]; ok {
return po
}
return m.PruneOptions
}

Просмотреть файл

@ -44,7 +44,11 @@ func TestReadManifest(t *testing.T) {
Constraint: gps.NewBranch("master"), Constraint: gps.NewBranch("master"),
}, },
}, },
Ignored: []string{"github.com/foo/bar"}, Ignored: []string{"github.com/foo/bar"},
PruneOptions: gps.PruneNestedVendorDirs | gps.PruneNonGoFiles,
PruneProjectOptions: gps.PruneProjectOptions{
gps.ProjectRoot("github.com/golang/dep"): gps.PruneNestedVendorDirs,
},
} }
if !reflect.DeepEqual(got.Constraints, want.Constraints) { if !reflect.DeepEqual(got.Constraints, want.Constraints) {
@ -78,6 +82,11 @@ func TestWriteManifest(t *testing.T) {
} }
m.Ignored = []string{"github.com/foo/bar"} m.Ignored = []string{"github.com/foo/bar"}
m.PruneOptions = gps.PruneNestedVendorDirs | gps.PruneNonGoFiles
m.PruneProjectOptions = gps.PruneProjectOptions{
gps.ProjectRoot("github.com/golang/dep"): gps.PruneNestedVendorDirs,
}
got, err := m.MarshalTOML() got, err := m.MarshalTOML()
if err != nil { if err != nil {
t.Fatalf("error while marshaling valid manifest to TOML: %q", err) t.Fatalf("error while marshaling valid manifest to TOML: %q", err)
@ -89,7 +98,7 @@ func TestWriteManifest(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} else { } else {
t.Errorf("valid manifest did not marshal to TOML as expected:\n\t(GOT): %s\n\t(WNT): %s", string(got), want) t.Errorf("valid manifest did not marshal to TOML as expected:\n(GOT):\n%s\n(WNT):\n%s", string(got), want)
} }
} }
} }
@ -368,6 +377,32 @@ func TestValidateManifest(t *testing.T) {
wantWarn: []error{errors.New("revision \"8d43f8c0b836\" should not be in abbreviated form")}, wantWarn: []error{errors.New("revision \"8d43f8c0b836\" should not be in abbreviated form")},
wantError: nil, wantError: nil,
}, },
{
name: "valid prune options",
tomlString: `
[[constraint]]
name = "github.com/foo/bar"
version = "1.0.0"
[prune]
non-go = true
`,
wantWarn: []error{},
wantError: nil,
},
{
name: "invalid root prune options",
tomlString: `
[[constraint]]
name = "github.com/foo/bar"
version = "1.0.0"
[prune]
non-go = false
`,
wantWarn: []error{},
wantError: errInvalidRootPruneValue,
},
} }
// contains for error // contains for error