Skip to content
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

feat(gnomod): forbid require and find dependencies without it #3123

Open
wants to merge 55 commits into
base: master
Choose a base branch
from

Conversation

n0izn0iz
Copy link
Contributor

@n0izn0iz n0izn0iz commented Nov 14, 2024

A step towards the importer package (#2932) and future of gno.mod (#2904)

  • BREAKING CHANGE: remove require statement support from gno.mod
  • use .gno files import statements to find dependencies
  • extract package download routines in gnovm/pkg/gnopkgfetch
  • add a GNO_PKG_HOSTS env variable that takes domain=rpcURL coma-separated pairs to override pkg fetching behavior
  • add and use a mocked rpc client that "fetch" packages from the examples dir
  • extract imports gathering utils in gnovm/pkg/gnoimports

I decided to do this first to avoid having multiple ways to resolve dependencies lying around in the codebase and causing confusion in subsequent steps

Contributors' checklist...
  • Added new tests, or not needed, or not feasible
  • Provided an example (e.g. screenshot) to aid review or the PR is self-explanatory
  • Updated the official documentation or not needed
  • No breaking changes were made, or a BREAKING CHANGE: xxx message was included in the description
  • Added references to related issues and PRs
  • Provided any useful hints for running manual tests

Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
Signed-off-by: Norman Meier <[email protected]>
@n0izn0iz n0izn0iz marked this pull request as draft November 14, 2024 18:47
@n0izn0iz
Copy link
Contributor Author

seems some tests are failing after merging master, my bad, will fix asap

@n0izn0iz n0izn0iz marked this pull request as ready for review November 15, 2024 07:10
@n0izn0iz
Copy link
Contributor Author

fixed

@Kouteki Kouteki added the in focus Core team is prioritizing this work label Nov 15, 2024
@zivkovicmilos zivkovicmilos added the breaking change Functionality that contains breaking changes label Nov 22, 2024
Copy link
Member

@zivkovicmilos zivkovicmilos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments, that you for the require removal 🙏

If this is the route we wanna go with, the PR I think accomplishes what it needs to accomplish.
Please check some comments for testing code, it's the only thing I believe we need to modify

res, err := gnoimports.PackageImports(root)
_ = err

entries, err := os.ReadDir(root)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this go before the PackageImports call?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we sort, it doesn't matter

sub := packageImportsRecursive(filepath.Join(root, entry.Name()))

for _, imp := range sub {
if !slices.Contains(res, imp) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just about to ask how we filter duplicates

This is probably alright, I'd be curious to see how performant this is in comparison to a map lookup

Copy link
Contributor Author

@n0izn0iz n0izn0iz Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

map is faster for this after around 1K import statements: https://gist.github.com/n0izn0iz/3551867b864ef9ca155230a7ea8bb120
do you want me to replace with a map lookup?

}
}

sort.Strings(res)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do they need to be sorted?

Copy link
Contributor Author

@n0izn0iz n0izn0iz Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code replaces getting the require list of a module and go mod tidy sorts imports. It might not be needed in this case though

require.Equal(t, expected, imports)
}

func createTmpDir(t *testing.T) (string, func()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not simply use t.TempDir()?

Copy link
Contributor Author

@n0izn0iz n0izn0iz Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was moved from gnovm/cmd/gno/mod_test.go

Refactored in ed89ff6

seen := make(map[string]struct{})
for _, e := range entries {
filename := e.Name()
if !strings.HasSuffix(filename, ".gno") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have these values as constants anywhere?
I swear we repeat this string literal 9000 times in the codebase

Copy link
Contributor Author

@n0izn0iz n0izn0iz Nov 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look like it, I wanted to create a gnofiles package with these constants and related utils but it felt out of scope

}

// not using gno client due to cyclic dep
func qfile(tmClient tm2client.Client, pkgPath string) ([]byte, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have a client method for something like this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't seem so, gnoweb does something very similar here

btw the comment "not using gno client due to cyclic dep" was some confusion by me since gnoclient is a pkg dedicated to mutations, not queries

// we need to know that gno.land/r/foo is not downloaded.
// We do this by checking for the presence of gno.land/r/foo/gno.mod
if err := os.WriteFile(modFilePath, []byte("module "+pkgPath+"\n"), 0o644); err != nil {
return fmt.Errorf("failed to write modfile at %q: %w", modFilePath, err)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no cleanup if this fails, this is why I suggest you break up this mega func

Copy link
Contributor Author

@n0izn0iz n0izn0iz Nov 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As was the case with the previous implementation

I agree with you, we also miss a filelock to avoid conflict between multiple gno command instances. I intend to improve all this in the subsequent PRs towards the unified package loader but I feel it's out of scope of this one

@@ -0,0 +1,99 @@
package gnopkgfetch
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not a test file, so it's compiled into the binary. Please change it 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to do it any other way, pls see #3123 (comment)

t.Fatalf("failed to get source path")
}
examplesDir := filepath.Join(filepath.Dir(filename), "..", "..", "..", "examples")
oldClient := fetchClient
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:(

We can do better 🙏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to ideas

examplesRoot string
}

func (m *examplesMockClient) SendRequest(ctx context.Context, request types.RPCRequest) (*types.RPCResponse, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we clean up this mock a bit? 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's very hard to read

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll clean up if you validate the new way it's injected :) otherwise I'll probably have to scrap it


res := ctypes.ResultABCIQuery{}

finfo, err := os.Stat(target)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
}

if finfo.IsDir() {
entries, err := os.ReadDir(target)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
}
res.Response.Data = []byte(strings.Join(files, "\n"))
} else {
content, err := os.ReadFile(target)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression High

This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.
This path depends on a
user-provided value
.

res := ctypes.ResultABCIQuery{}

finfo, err := os.Stat(target)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1). This path depends on a [user-provided value](2). This path depends on a [user-provided value](3). This path depends on a [user-provided value](4).
}

if finfo.IsDir() {
entries, err := os.ReadDir(target)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1). This path depends on a [user-provided value](2). This path depends on a [user-provided value](3). This path depends on a [user-provided value](4).
}
res.Response.Data = []byte(strings.Join(files, "\n"))
} else {
content, err := os.ReadFile(target)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1). This path depends on a [user-provided value](2). This path depends on a [user-provided value](3). This path depends on a [user-provided value](4).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking change Functionality that contains breaking changes in focus Core team is prioritizing this work 📦 ⛰️ gno.land Issues or PRs gno.land package related 📦 🤖 gnovm Issues or PRs gnovm related 🧾 package/realm Tag used for new Realms or Packages.
Projects
Status: In Review
Development

Successfully merging this pull request may close these issues.

4 participants