-
Notifications
You must be signed in to change notification settings - Fork 10.2k
Use depguard linter to forbid dependencies #20721
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Use depguard linter to forbid dependencies #20721
Conversation
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: ivanvc The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
3367a8b
to
2db0501
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted filessee 37 files with indirect coverage changes @@ Coverage Diff @@
## main #20721 +/- ##
==========================================
- Coverage 70.09% 69.16% -0.94%
==========================================
Files 404 420 +16
Lines 34250 34817 +567
==========================================
+ Hits 24008 24080 +72
- Misses 8850 9343 +493
- Partials 1392 1394 +2 Continue to review full report in Codecov by Sentry.
🚀 New features to boost your workflow:
|
/retest |
- '**/api/**/*.go' | ||
- '!**/server/etcdserver/api/**/*.go' | ||
deny: | ||
- pkg: go.etcd.io/etcd/api/v3$ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$ at the end is unique. What's it purpose?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem is the next import:
pb "go.etcd.io/etcd/api/v3/etcdserverpb" |
If we remove the $
, it fails:
% (cd api && 'golangci-lint' 'run' '--config' '/home/ivan/Code/Personal/etcd/etcd/tools/.golangci.yaml' '--show-stats=false' './...')
FAIL: (code:1):
% (cd api && 'golangci-lint' 'run' '--config' '/home/ivan/Code/Personal/etcd/etcd/tools/.golangci.yaml' '--show-stats=false' './...')
../api/etcdserverpb/raft_internal_stringer_test.go:22:2: import 'go.etcd.io/etcd/api/v3/etcdserverpb' is not allowed from list 'api' (depguard)
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you tested the rules? I tried but in my 3 tries I always hit import cycle.
This is actually another way to test it. Remove the dollar sign, and run make verify-lint
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof, I removed by accident the trailing dollar sign 😅, and this is the error: https://prow.k8s.io/view/gs/kubernetes-ci-logs/pr-logs/pull/etcd-io_etcd/20721/pull-etcd-verify/1971396972069785600
tools/.golangci.yaml
Outdated
- "**/etcdctl/**/*.go" | ||
- "**/etcdutl/**/*.go" | ||
- "**/server/**/*.go" | ||
- "!**/tests/antithesis/server/**/*.go" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are no go files in tests/antithesis/server
it's just Dockerfile for building instrumented server image.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I'll remove this line.
Have you tested the rules? I tried but in my 3 tries I always hit import cycle. |
tools/.golangci.yaml
Outdated
- pkg: go.etcd.io/etcd/pkg/v3 | ||
- pkg: go.etcd.io/etcd/v3 | ||
- pkg: go.etcd.io/tests/v3 | ||
- pkg: go.etcd.io/v3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- pkg: go.etcd.io/v3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure of the original motivation behind these lines. I just copied and pasted (and fixed). Please refer to: https://github.com/etcd-io/etcd/pull/20721/files#diff-ba0784a950902285760f839a4ad720a381b2981e8a67147f5f43f7508e6343ed
// Bad imports are sometimes causing attempts to pull that code.
// This makes the error more explicit.
replace (
go.etcd.io/etcd => ./FORBIDDEN_DEPENDENCY
go.etcd.io/etcd/api/v3 => ./FORBIDDEN_DEPENDENCY
go.etcd.io/etcd/pkg/v3 => ./FORBIDDEN_DEPENDENCY
go.etcd.io/etcd/tests/v3 => ./FORBIDDEN_DEPENDENCY
go.etcd.io/etcd/v3 => ./FORBIDDEN_DEPENDENCY
)
For me, it doesn't make sense to have go.etcd.io/etcd
. If I understand correctly, we couldn't import that package as it would try to import etcd v1. At the bottom, we have go.etcd.io/etcd/v3
.
Therefore, I removed go.etcd.io/etcd
, and kept go.etcd.io/etcd/v3
.
Could you please explain why you're suggesting deleting go.etcd.io/etcd/v3
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please explain why you're suggesting deleting
go.etcd.io/etcd/v3
as well?
I am suggesting to delete go.etcd.io/v3
as we don't have this module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, right. My bad 🤦
- '**/api/**/*.go' | ||
- '!**/server/etcdserver/api/**/*.go' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we separate configuration for different modules?
I suggest that we define separate rules for each module, following exactly the same way as we do now in each go.mod file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we can separate the configuration per module. I initially had it that way, but then it looked very repetitive, and I thought of consolidating etcdutl
, etcdtl
, and server
as they have the same rule.
However, what you're looking at is a depguard
limitation. The path needs to be absolute (refer to OpenPeeDeeP/depguard#54).
So, breaking down these two lines:
'**/api/**/*.go'
: Any go file that is in a path with/api/
. Note that this would match any files insideserver/etcdserver/api
'!**/server/etcdserver/api/**/*.go'
: Deny files that have a path like/server/etcdserver/api
. Emphasis on the exclamation mark.
You can see the same configuration in other projects. i.e., this is from woodpecker: https://github.com/woodpecker-ci/woodpecker/blob/388557d94aa9fe06841a207eba4ffa4ba0ae5a05/.golangci.yaml#L109-L112
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However, what you're looking at is a
depguard
limitation. The path needs to be absolute (refer to OpenPeeDeeP/depguard#54).
do not get time to deep it for now. May take a close look sometime later.
But my immediate feeling is that it's more complicated & hard to maintain if we mix different modules.
but then it looked very repetitive
repetitive
should be a minor concern. It's much much clearer and easy to maintain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, I'll change it, but be aware that we'll still need the negation !
paths due to depguard's limitation with paths.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, I did split etcdctl, etcdutl, and server, which were sharing the configuration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each time a new package is added, we need to verify the depguard settings
How does it differ from the current setup? We need to verify FORBIDDEN_DEPENDENCY clauses, it's as easy to miss. For example we haven't really added FORBIDDEN_DEPENDENCY clauses for cache package https://github.com/etcd-io/etcd/blob/main/cache/go.mod
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to verify FORBIDDEN_DEPENDENCY clauses, it's as easy to miss.
The replace xxx => FORBIDDEN_DEPENDENCY
is added in module level (in go.mod files); it should be stable. But depguard
add rules in package & files level, which is less stable (as we may add new packages)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way (the current approach) or a linter, would require manual intervention when we create new packages. As far as I know, there's nothing that would magically work without us adding the forbidden imports.
Either is also prone to errors. I still can't understand why we're forbidding go.etcd.io/etcd
. Because if I understand correctly, that package is v1
.
If what we don't like about depguard is how they define the list of files. When I did my research I found that:
- The
revive
linter hasimports-blocklist
. Unfortunately there's no way to specify it per module or per file path (like depguard). - There's another linter, gomodguard. It's available through golangci-lint. But it has the same issue as
revive
.- It works at a module level per its documentation
The linter looks for blocked modules in go.mod and searches for imported packages where the imported packages module is blocked. Indirect modules are not considered.
- As it doesn't have a way to configure paths we would need to use it stand-alone, we can add a
.gomodguard.yaml
in each module.- Pro: This is very similar to how we currently do it
- Cons:
- Adds a new linter to execute, as it needs to be called outside of golangci-lint (maybe not a con, but a change we need)
- This linter wouldn't be possible to call from the top-level module once we integrate the Go workspace.
- It works at a module level per its documentation
I'll open a PR with the gomodguard approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- There's another linter, gomodguard. It's available through golangci-lint.
It seems that we can't define rules for multiple modules in .golangci.yaml. It should be fine. Adding a separate .gomodguard.yaml
for each module is fine.
2. This linter wouldn't be possible to call from the top-level module once we integrate the Go workspace.
Not sure I understood this. There is no golang source code in the top-level module, nor any replace xxx => FORBIDDEN_DEPENDENCY
directives in https://github.com/etcd-io/etcd/blob/main/go.mod
Anyway, the PR #20748 looks good to me. To double confirm, can it clear the blocker to use go workspace as you mentioned in #20721 (comment)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This linter wouldn't be possible to call from the top-level module once we integrate the Go workspace.
Not sure I understood this. There is no golang source code in the top-level module, nor any
replace xxx => FORBIDDEN_DEPENDENCY
directives in https://github.com/etcd-io/etcd/blob/main/go.mod
This is a minor inconvenience whenever we have the Go workspace. With the workspace, you can run (once):
$ golangci-lint ./...
From the top-level of the repository.
With this linter configured per module, we'll still need to do
$ cd module && gomodguard ./...
Anyway, the PR #20748 looks good to me. To double confirm, can it clear the blocker to use go workspace as you mentioned in #20721 (comment)?
Yes, that would clear the go.mod
replaces blocker.
- "**/pkg/**/*.go" | ||
- "!**/client/pkg/**/*.go" | ||
- "!**/tools/rw-heatmaps/pkg/**/*.go" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggest not to mixes multiple modules.
ditto as #20721 (comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. This is not mixing multiple modules, it's targeting only the root pkg
, excluding other pkg
directories in the repo.
@serathius, yes. It's challenging to avoid falling into the cycle. But refer to: #19423 (comment) |
2db0501
to
a133bbc
Compare
250683b
to
1ab5edf
Compare
Introduce the depguard linter to forbid required dependencies in the submodules. This is a clean way to achieve what we had before in the go.mod by using a replacement and pointing the forbidden package to a virtual non-existent directory "FORBIDDEN_DEPENDENCY". Now, if a package requires a forbidden dependency, the linter will fail, rather than having Go fail to compile/fetch dependencies. Signed-off-by: Ivan Valdes <[email protected]>
1ab5edf
to
5692026
Compare
/retest |
Introduce the depguard linter to forbid required dependencies in the submodules. This is a clean way to achieve what we had before in the go.mod by using a replacement and pointing the forbidden package to a virtual non-existent directory "FORBIDDEN_DEPENDENCY". Now, if a package requires a forbidden dependency, the linter will fail, rather than having Go fail to compile/fetch dependencies.
Branched out from #19423.
Link to: #18409.
Please read https://github.com/etcd-io/etcd/blob/main/CONTRIBUTING.md#contribution-flow.