1
0
mirror of https://github.com/ko-build/ko.git synced 2025-02-07 19:30:23 +02:00

Remove --watch mode (#585)

This commit is contained in:
Jason Hall 2022-03-03 14:58:34 -05:00 committed by GitHub
parent 967e3ebb2d
commit cd41b3e714
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 1 additions and 1078 deletions

View File

@ -83,7 +83,6 @@ ko apply -f FILENAME [flags]
--token string Bearer token for authentication to the API server (DEPRECATED)
--user string The name of the kubeconfig user to use (DEPRECATED)
--username string Username for basic authentication to the API server (DEPRECATED)
-W, --watch Continuously monitor the transitive dependencies of the passed yaml files, and redeploy whenever anything changes. (DEPRECATED)
```
### Options inherited from parent commands

View File

@ -83,7 +83,6 @@ ko create -f FILENAME [flags]
--token string Bearer token for authentication to the API server (DEPRECATED)
--user string The name of the kubeconfig user to use (DEPRECATED)
--username string Username for basic authentication to the API server (DEPRECATED)
-W, --watch Continuously monitor the transitive dependencies of the passed yaml files, and redeploy whenever anything changes. (DEPRECATED)
```
### Options inherited from parent commands

View File

@ -58,7 +58,6 @@ ko resolve -f FILENAME [flags]
--tag-only Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
-t, --tags strings Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest])
--tarball string File to save images tarballs
-W, --watch Continuously monitor the transitive dependencies of the passed yaml files, and redeploy whenever anything changes. (DEPRECATED)
```
### Options inherited from parent commands

2
go.mod
View File

@ -11,11 +11,9 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.11.1
github.com/docker/docker v20.10.12+incompatible
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960
github.com/fsnotify/fsnotify v1.5.1
github.com/go-training/helloworld v0.0.0-20200225145412-ba5f4379d78b
github.com/google/go-cmp v0.5.7
github.com/google/go-containerregistry v0.8.1-0.20220209165246-a44adc326839
github.com/mattmoor/dep-notify v0.0.0-20190205035814-a45dec370a17
github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198
github.com/sigstore/cosign v1.3.2-0.20211120003522-90e2dcfe7b92
github.com/spf13/cobra v1.3.0

2
go.sum
View File

@ -1310,8 +1310,6 @@ github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kN
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattmoor/dep-notify v0.0.0-20190205035814-a45dec370a17 h1:P91eDVgVzvF2EmA6fmGCyR2VQFlmo2nsmS2DbHoGAco=
github.com/mattmoor/dep-notify v0.0.0-20190205035814-a45dec370a17/go.mod h1:qWnF4u+oS4UWOZMwZcBQXrt5IQIdWc6XVJLDdxGIfdQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=

View File

@ -19,25 +19,13 @@ import (
"os"
"path/filepath"
"github.com/fsnotify/fsnotify"
"github.com/spf13/cobra"
)
const deprecation412 = `NOTICE!
-----------------------------------------------------------------
Watch mode is deprecated and unsupported, and will be removed in
a future release.
For more information see:
https://github.com/google/ko/issues/412
-----------------------------------------------------------------
`
// FilenameOptions is from pkg/kubectl.
type FilenameOptions struct {
Filenames []string
Recursive bool
Watch bool
}
func AddFileArg(cmd *cobra.Command, fo *FilenameOptions) {
@ -46,8 +34,6 @@ func AddFileArg(cmd *cobra.Command, fo *FilenameOptions) {
"Filename, directory, or URL to files to use to create the resource")
cmd.Flags().BoolVarP(&fo.Recursive, "recursive", "R", fo.Recursive,
"Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.")
cmd.Flags().BoolVarP(&fo.Watch, "watch", "W", fo.Watch,
"Continuously monitor the transitive dependencies of the passed yaml files, and redeploy whenever anything changes. (DEPRECATED)")
}
// Based heavily on pkg/kubectl
@ -56,19 +42,6 @@ func EnumerateFiles(fo *FilenameOptions) chan string {
go func() {
// When we're done enumerating files, close the channel
defer close(files)
// When we are in --watch mode, we set up watches on the filesystem locations
// that we are supplied and continuously stream files, until we are sent an
// interrupt.
var watcher *fsnotify.Watcher
if fo.Watch {
log.Print(deprecation412)
var err error
watcher, err = fsnotify.NewWatcher()
if err != nil {
log.Fatalf("Unexpected error initializing fsnotify: %v", err)
}
defer watcher.Close()
}
for _, paths := range fo.Filenames {
// Just pass through '-' as it is indicative of stdin.
if paths == "-" {
@ -90,9 +63,6 @@ func EnumerateFiles(fo *FilenameOptions) chan string {
if path != paths && !fo.Recursive {
return filepath.SkipDir
}
if watcher != nil {
watcher.Add(path)
}
// We don't stream back directories, we just decide to skip them, or not.
return nil
}
@ -105,14 +75,6 @@ func EnumerateFiles(fo *FilenameOptions) chan string {
default:
return nil
}
// We weren't passed this explicitly, so elide the watch as we
// are already watching the directory.
} else {
// We were passed this directly, and so we may not be watching the
// directory, so watch this file explicitly.
if watcher != nil {
watcher.Add(path)
}
}
files <- path
@ -122,23 +84,6 @@ func EnumerateFiles(fo *FilenameOptions) chan string {
log.Fatalf("Error enumerating files: %v", err)
}
}
// We're done watching the files we were passed and setting up watches.
// Now listen for change events from the watches we set up and resend
// files that change as if we just saw them (so they can be reprocessed).
if watcher != nil {
for {
select {
case event := <-watcher.Events:
switch filepath.Ext(event.Name) {
case ".json", ".yaml":
files <- event.Name
}
case err := <-watcher.Errors:
log.Fatalf("Error watching: %v", err)
}
}
}
}()
return files
}

View File

@ -23,14 +23,12 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"path"
"strings"
"sync"
"github.com/google/go-containerregistry/pkg/name"
"github.com/mattmoor/dep-notify/pkg/graph"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/labels"
@ -150,9 +148,6 @@ func makeBuilder(ctx context.Context, bo *options.BuildOptions) (*build.Caching,
// - if a valid Build future exists at the time of the request,
// then block on it.
// - if it does not, then initiate and record a Build future.
// - When import paths are "affected" by filesystem changes during a
// Watch, then invalidate their build futures *before* we put the
// affected yaml files onto the channel
//
// This will benefit the following key cases:
// 1. When the same import path is referenced across multiple yaml files
@ -295,38 +290,6 @@ func resolveFilesToWriter(
// This tracks filename -> []importpath
var sm sync.Map
var g graph.Interface
var errCh chan error
var err error
if fo.Watch {
// Start a dep-notify process that on notifications scans the
// file-to-recorded-build map and for each affected file resends
// the filename along the channel.
g, errCh, err = graph.New(func(ss graph.StringSet) {
sm.Range(func(k, v interface{}) bool {
key := k.(string)
value := v.([]string)
for _, ip := range value {
// dep-notify doesn't understand the ko:// prefix
ip := strings.TrimPrefix(ip, build.StrictScheme)
if ss.Has(ip) {
// See the comment above about how "builder" works.
// Always use ko:// for the builder.
builder.Invalidate(build.StrictScheme + ip)
fs <- key
}
}
return true
})
})
if err != nil {
return fmt.Errorf("creating dep-notify graph: %w", err)
}
// Cleanup the fsnotify hooks when we're done.
defer g.Shutdown()
}
// This tracks resolution errors and ensures we cancel other builds if an
// individual build fails.
errs, ctx := errgroup.WithContext(ctx)
@ -376,33 +339,11 @@ func resolveFilesToWriter(
if err != nil {
// This error is sometimes expected during watch mode, so this
// isn't fatal. Just print it and keep the watch open.
err := fmt.Errorf("error processing import paths in %q: %w", f, err)
if fo.Watch {
log.Print(err)
return nil
}
return err
return fmt.Errorf("error processing import paths in %q: %w", f, err)
}
// Associate with this file the collection of binary import paths.
sm.Store(f, recordingBuilder.ImportPaths)
ch <- b
if fo.Watch {
for _, ip := range recordingBuilder.ImportPaths {
// dep-notify doesn't understand the ko:// prefix
ip := strings.TrimPrefix(ip, build.StrictScheme)
// Technically we never remove binary targets from the graph,
// which will increase our graph's watch load, but the
// notifications that they change will result in no affected
// yamls, and no new builds or deploys.
if err := g.Add(ip); err != nil {
// If we're in watch mode, just fail.
err := fmt.Errorf("adding importpath %q to dep graph: %w", ip, err)
errCh <- err
return err
}
}
}
return nil
})
@ -418,9 +359,6 @@ func resolveFilesToWriter(
// be applied.
out.Write(append(b, []byte("\n---\n")...))
}
case err := <-errCh:
return fmt.Errorf("watching dependencies: %w", err)
}
}

View File

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,19 +0,0 @@
/*
Copyright 2018 Matt Moore
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package graph holds the utilities for building/maintaining the dependency
// graph and notifying on changes.
package graph

View File

@ -1,31 +0,0 @@
/*
Copyright 2018 Matt Moore
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package graph
// Interface for manipulating the dependency graph.
type Interface interface {
// This adds a given importpath to the collection of roots that we are tracking.
Add(importpath string) error
// Shutdown stops tracking all Add'ed import paths for changes.
Shutdown() error
}
// Observer is the type for the callback that will happen on changes.
// The callback is supplied with the transitive dependents (aka "affected
// targets") of the file that has changed.
type Observer func(affected StringSet)

View File

@ -1,343 +0,0 @@
/*
Copyright 2018 Matt Moore
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package graph
import (
"fmt"
gb "go/build"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/fsnotify/fsnotify"
)
// New creates a new Interface for building up dependency graphs.
// It starts in the provided working directory, and will call the provided
// Observer for any changes.
//
// The returned graph is empty, but new targets may be added via the returned
// Interface. New also returns any immediate errors, and a channel through which
// errors watching for changes in the dependencies will be returned until the
// graph is Shutdown.
// // Create our empty graph
// g, errCh, err := New(...)
// if err != nil { ... }
// // Cleanup when we're done. This closes errCh.
// defer g.Shutdown()
// // Start tracking this target.
// err := g.Add("github.com/mattmoor/warm-image/cmd/controller")
// if err != nil { ... }
// select {
// case err := <- errCh:
// // Handle errors that occur while watching the above target.
// case <-stopCh:
// // When some stop signal happens, we're done.
// }
func New(obs Observer) (Interface, chan error, error) {
return NewWithOptions(obs, DefaultOptions...)
}
var DefaultOptions = []Option{WithCurrentDirectory, WithContext(&gb.Default), WithFSNotify,
WithFileFilter(OmitTest, OmitNonGo), WithPackageFilter(OmitVendor), WithOutsideWorkDirFilter}
func NewWithOptions(obs Observer, opts ...Option) (Interface, chan error, error) {
m := &manager{
packages: make(map[string]*node),
}
for _, opt := range opts {
if err := opt(m); err != nil {
if m.watcher != nil {
m.watcher.Close()
}
return nil, nil, err
}
}
// Start listening for events via the filesystem watcher.
go func() {
for {
event, ok := <-m.eventCh
if !ok {
// When the channel has been closed, the watcher is shutting down
// and we should return to cleanup the go routine.
return
}
// Apply our file filters to improve the signal-to-noise.
skip := false
for _, f := range m.fileFilters {
if f(event.Name) {
skip = true
}
}
if skip {
continue
}
// Determine what package contains this file
// and signal the change. Call our Observer
// on affected targets when we're done.
if n := m.enclosingPackage(event.Name); n != nil {
m.onChange(n, func(n *node) {
obs(m.affectedTargets(n))
})
}
}
}()
return m, m.errCh, nil
}
// notification is a callback that internal consumers of manager may use to get
// a crack at a node after it has been updated.
type notification func(*node)
// empty implements notification and does nothing.
func empty(n *node) {}
type watcher interface {
Add(string) error
Remove(string) error
Close() error
}
type manager struct {
m sync.Mutex
ctx *gb.Context
pkgFilters []PackageFilter
fileFilters []FileFilter
packages map[string]*node
watcher watcher
// The working directory relative to which import paths are evaluated.
workdir string
errCh chan error
eventCh chan fsnotify.Event
}
// manager implements Interface
var _ Interface = (*manager)(nil)
// manager implements fmt.Stringer
var _ fmt.Stringer = (*manager)(nil)
// Add implements Interface
func (m *manager) Add(importpath string) error {
m.m.Lock()
defer m.m.Unlock()
_, _, err := m.add(importpath, nil)
return err
}
// add adds the provided importpath (if it doesn't exist) and optionally
// adds the dependent node (if provided) as a dependent of the target.
// This returns the node for the provided importpath, whether the dependency
// structure has changed, and any errors that may have occurred adding the node.
func (m *manager) add(importpath string, dependent *node) (*node, bool, error) {
// INVARIANT m.m must be held to call this.
if pkg, ok := m.packages[importpath]; ok {
return pkg, pkg.addDependent(dependent), nil
}
// New nodes always start as a simple shell, then we set up the
// fsnotify and immediate simulate a change to prompt the package
// to load its data. A good analogy would be how the "diff" in
// a code review for new files looks like everything being added;
// so too does this first simulated change pull in the rest of the
// dependency graph.
newNode := &node{
name: importpath,
}
m.packages[importpath] = newNode
newNode.addDependent(dependent)
// Load the package once to determine it's filesystem location,
// and set up a watch on that location.
pkg, err := m.ctx.Import(importpath, m.workdir, gb.ImportComment)
if err != nil {
newNode.removeDependent(dependent)
delete(m.packages, importpath)
return nil, false, err
}
if err := m.watcher.Add(pkg.Dir); err != nil {
newNode.removeDependent(dependent)
delete(m.packages, importpath)
return nil, false, err
}
newNode.dir = pkg.Dir
// This is done via go routine so that it can take over the lock.
go m.onChange(newNode, empty)
return newNode, true, nil
}
// affectedTargets returns the set of targets that would be affected by a
// change to the target represented by the given node. This set is comprised
// of the transitive dependents of the node, including itself.
func (m *manager) affectedTargets(n *node) StringSet {
m.m.Lock()
defer m.m.Unlock()
return n.transitiveDependents()
}
// enclosingPackage returns the node for the package covering the
// watched path.
func (m *manager) enclosingPackage(path string) *node {
m.m.Lock()
defer m.m.Unlock()
dir := filepath.Dir(path)
for _, v := range m.packages {
if strings.HasSuffix(dir, v.dir) {
return v
}
}
return nil
}
// onChange updates the graph based on the current state of the package
// represented by the given node. Once the graph has been updated, the
// notification function is called on the node.
func (m *manager) onChange(changed *node, not notification) {
m.m.Lock()
defer m.m.Unlock()
// Load the package information and update dependencies.
pkg, err := m.ctx.Import(changed.name, m.workdir, gb.ImportComment)
if err != nil {
m.errCh <- err
return
}
// haveDepsChanged := false
seen := make(StringSet)
for _, ip := range pkg.Imports {
if ip == "C" {
// skip cgo
continue
}
subpkg, err := m.ctx.Import(ip, m.workdir, gb.ImportComment)
if err != nil {
m.errCh <- err
return
}
skip := false
for _, f := range m.pkgFilters {
if f(subpkg) {
skip = true
break
}
}
if skip {
continue
}
n, chg, err := m.add(subpkg.ImportPath, changed)
if err != nil {
m.errCh <- err
return
} else if chg {
// haveDepsChanged = true
}
if changed.addDependency(n) {
// haveDepsChanged = true
}
seen.Add(subpkg.ImportPath)
}
// Remove dependencies that we no longer have.
removed := changed.removeUnseenDependencies(seen)
if len(removed) > 0 {
// haveDepsChanged = true
}
for _, dependency := range removed {
d := dependency
go m.maybeGC(d)
}
// log.Printf("Processing %s, have deps changed: %v", changed.name, haveDepsChanged)
// Done via go routine so that we can be passed a callback that
// takes the lock on manager.
go not(changed)
}
func (m *manager) maybeGC(n *node) {
m.m.Lock()
defer m.m.Unlock()
if len(n.dependents) > 0 {
// It has dependents, so it should not be removed.
return
}
// If it has zero dependents, then remove it from the packages map.
delete(m.packages, n.name)
// Lookup the package information, so that we know what directory to stop watching.
subpkg, err := m.ctx.Import(n.name, m.workdir, gb.ImportComment)
if err != nil {
m.errCh <- err
return
}
// Remove the watch on the package's directory
if err := m.watcher.Remove(subpkg.Dir); err != nil {
m.errCh <- err
return
}
for _, dependency := range n.dependencies {
dependency.removeDependent(n)
d := dependency
go m.maybeGC(d)
}
}
// Shutdown implements Interface.
func (m *manager) Shutdown() error {
return m.watcher.Close()
}
// String implements fmt.Stringer
func (m *manager) String() string {
m.m.Lock()
defer m.m.Unlock()
// WTB Topo sort.
order := []string{}
for k := range m.packages {
order = append(order, k)
}
sort.Strings(order)
parts := []string{}
for _, key := range order {
parts = append(parts, m.packages[key].String())
}
return strings.Join(parts, "\n")
}

View File

@ -1,119 +0,0 @@
/*
Copyright 2018 Matt Moore
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package graph
import (
gb "go/build"
"os"
"path/filepath"
"strings"
"github.com/fsnotify/fsnotify"
)
// PackageFilter is the type of functions that determine whether to omit a
// particular Go package from the dependency graph.
type PackageFilter func(*gb.Package) bool
// OmitVendor implements PackageFilter to exclude packages in the vendor directory.
func OmitVendor(pkg *gb.Package) bool {
return strings.Contains(pkg.ImportPath, "/vendor/")
}
// FileFilter is the type of functions that determine whether to omit a particular
// file from triggering a package-level event.
type FileFilter func(string) bool
// OmitNonGo implements FileFilter to exclude non-Go files from triggering package
// change notifications.
func OmitNonGo(path string) bool {
return filepath.Ext(path) != ".go"
}
// OmitTests implements FileFilter to exclude Go test files from triggering package
// change notifications.
func OmitTest(path string) bool {
return strings.HasSuffix(path, "_test.go")
}
// Option is used to mutate the underlying graph implementation during construction.
// Since the implementation's type is private this may only be implemented from within
// this package.
type Option func(*manager) error
// WithWorkDir configures the graph to use the provided working directory.
func WithWorkDir(workdir string) Option {
return func(m *manager) error {
m.workdir = workdir
return nil
}
}
// WithCurrentDirectory configures the working directory to be the current directory
func WithCurrentDirectory(m *manager) error {
wd, err := os.Getwd()
if err != nil {
return err
}
return WithWorkDir(wd)(m)
}
// WithContext configures the graph to use the provided Go build context.
func WithContext(ctx *gb.Context) Option {
return func(m *manager) error {
m.ctx = ctx
return nil
}
}
// WithFSNotify configures the graph to use fsnotify to implement its watcher and error channel.
func WithFSNotify(m *manager) error {
watcher, err := fsnotify.NewWatcher()
if err != nil {
return err
}
m.watcher = watcher
m.errCh = watcher.Errors
m.eventCh = watcher.Events
return nil
}
// WithFileFilter configures the graph implementation with the provided file filters.
func WithFileFilter(ff ...FileFilter) Option {
return func(m *manager) error {
m.fileFilters = append(m.fileFilters, ff...)
return nil
}
}
// WithPackageFilter configures the graph implementation with the provided package filters.
func WithPackageFilter(ff ...PackageFilter) Option {
return func(m *manager) error {
m.pkgFilters = append(m.pkgFilters, ff...)
return nil
}
}
// WithOutsideWorkDirFilter configures the graph with a package filter that omits files outside
// of the configured working directory.
func WithOutsideWorkDirFilter(m *manager) error {
m.pkgFilters = append(m.pkgFilters, func(pkg *gb.Package) bool {
return !strings.HasPrefix(pkg.Dir, m.workdir)
})
return nil
}

View File

@ -1,185 +0,0 @@
/*
Copyright 2018 Matt Moore
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package graph
import (
"fmt"
)
// node holds a single node in our dependency graph and its immediately adjacent neighbors.
type node struct {
// The canonical import path for this node.
name string
// The directory from which we pull this import path.
dir string
// The dependency structure
dependencies []*node
dependents []*node
}
// node implements fmt.Stringer
var _ fmt.Stringer = (*node)(nil)
// String implements fmt.Stringer
func (n *node) String() string {
return fmt.Sprintf(`---
name: %s
depdnt: %v
depdncy: %v`, n.name, names(n.dependents), names(n.dependencies))
}
// addDependent adds the given dependent to the list of dependents for
// the given dependency. Dependent may be nil.
// This returns whether the node's neighborhood changes.
// The manager's lock must be held before calling this.
func (dependency *node) addDependent(dependent *node) bool {
if dependent == nil {
return false
}
for _, depdnt := range dependency.dependents {
if depdnt == dependent {
// Already a dependent
return false
}
}
// log.Printf("Adding %s <- %s", dependent.name, dependency.name)
dependency.dependents = append(dependency.dependents, dependent)
return true
}
// addDependency adds the given dependency to the list of dependencies for
// the given dependent. Neither parameter may be nil.
// This returns whether the node's neighborhood changes.
// The manager's lock must be held before calling this.
func (dependent *node) addDependency(dependency *node) bool {
for _, depdcy := range dependent.dependencies {
if depdcy == dependency {
// Already a dependency
return false
}
}
// log.Printf("Adding %s -> %s", dependent.name, dependency.name)
dependent.dependencies = append(dependent.dependencies, dependency)
return true
}
// removeDependent removes the given dependent to the list of dependents for
// the given dependency. Dependent may be nil.
// This returns whether the node's neighborhood changes.
// The manager's lock must be held before calling this.
func (dependency *node) removeDependent(dependent *node) bool {
for i, depdnt := range dependency.dependents {
if depdnt == dependent {
dependency.dependents = append(
dependency.dependents[:i], dependency.dependents[i+1:]...)
return true
}
}
return false
}
// removeDependency removes the given dependency to the list of dependencies for
// the given dependent. Neither parameter may be nil.
// This returns whether the node's neighborhood changes.
// The manager's lock must be held before calling this.
func (dependent *node) removeDependency(dependency *node) bool {
for i, depdcy := range dependent.dependencies {
if depdcy == dependency {
dependent.dependencies = append(
dependent.dependencies[:i], dependent.dependencies[i+1:]...)
return true
}
}
return false
}
// removeUnseenDependencies removes dependencies that aren't in the list of seen
// names. Returns the list of nodes that correspond to the named dependencies removed.
func (dependent *node) removeUnseenDependencies(seen StringSet) []*node {
keep := make([]*node, 0, len(dependent.dependencies))
toss := make([]*node, 0, len(dependent.dependencies))
for _, dep := range dependent.dependencies {
if seen.Has(dep.name) {
keep = append(keep, dep)
} else {
toss = append(toss, dep)
dependent.removeDependency(dep)
dep.removeDependent(dependent)
}
}
dependent.dependencies = keep
return toss
}
// transitiveDependents collects the set of node names transitively reachable
// by following the "dependents" edges.
func (n *node) transitiveDependents() StringSet {
return n.transitiveFoo(func(n *node) []*node {
return n.dependents
})
}
// transitiveDependencies collects the set of node names transitively reachable
// by following the "dependencies" edges.
func (n *node) transitiveDependencies() StringSet {
return n.transitiveFoo(func(n *node) []*node {
return n.dependencies
})
}
// neighbors defines a function for returning the neighbors of a
// particular node.
type neighbors func(*node) []*node
// transitiveDependents collects the set of node names transitively reachable
// by following the edges determines by the "neighbors" function.
func (n *node) transitiveFoo(nbr neighbors) StringSet {
// We will use "set" as the visited group for our DFS.
set := make(StringSet)
queue := []*node{n}
for len(queue) != 0 {
// Pop the top element off of the queue.
top := queue[len(queue)-1]
queue = queue[:len(queue)-1]
// Check/Mark visited
if set.Has(top.name) {
continue
}
set.Add(top.name)
// Append this node's dependents to our search.
queue = append(queue, nbr(top)...)
}
return set
}
// names returns the deduplicated and sorted names of the provided nodes.
func names(ns []*node) []string {
ss := make(StringSet, len(ns))
for _, n := range ns {
ss.Add(n.name)
}
return ss.InOrder()
}

View File

@ -1,51 +0,0 @@
/*
Copyright 2018 Matt Moore
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package graph
import (
"sort"
)
// StringSet is a simple abstraction for holding a collection of deduplicated strings.
type StringSet map[string]struct{}
// Add inserts a provided key into our set.
func (ss *StringSet) Add(key string) {
(*ss)[key] = struct{}{}
}
// Remove deletes a provided key from our set.
func (ss *StringSet) Remove(key string) {
delete(*ss, key)
}
// Has returns whether the set contains the provided key.
func (ss *StringSet) Has(key string) bool {
_, ok := (*ss)[key]
return ok
}
// InOrder returns the keys of the set in the ordering determined by sort.Strings.
func (ss StringSet) InOrder() []string {
keys := make([]string, 0, len(ss))
for k := range ss {
keys = append(keys, k)
}
sort.Strings(keys)
return keys
}

4
vendor/modules.txt vendored
View File

@ -175,7 +175,6 @@ github.com/evanphx/json-patch/v5
# github.com/form3tech-oss/jwt-go v3.2.5+incompatible
github.com/form3tech-oss/jwt-go
# github.com/fsnotify/fsnotify v1.5.1
## explicit
github.com/fsnotify/fsnotify
# github.com/go-logr/logr v1.2.0
github.com/go-logr/logr
@ -259,9 +258,6 @@ github.com/klauspost/compress/zstd
github.com/klauspost/compress/zstd/internal/xxhash
# github.com/magiconair/properties v1.8.5
github.com/magiconair/properties
# github.com/mattmoor/dep-notify v0.0.0-20190205035814-a45dec370a17
## explicit
github.com/mattmoor/dep-notify/pkg/graph
# github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-isatty
# github.com/mitchellh/go-homedir v1.1.0