Skip to content

Conversation

@manuelnaranjo
Copy link
Collaborator

Make sure the lock files don't contain circular dependencies, solving the current bugs

@github-actions
Copy link

github-actions bot commented Feb 27, 2025

⚠️ Optional job e2e-bzlmod-toolchain-circular-dependencies failed ⚠️

  • exit status: 1

@manuelnaranjo
Copy link
Collaborator Author

⚠️ Optional job e2e-bzlmod-toolchain-circular-dependencies failed ⚠️

  • exit status: 1

Actually no, but fine this job is going away

@manuelnaranjo manuelnaranjo marked this pull request as ready for review February 27, 2025 10:39
@manuelnaranjo manuelnaranjo force-pushed the mnaranjo/fix-circular-dependencies branch 3 times, most recently from b89d6cf to 2b54fb0 Compare February 28, 2025 08:48
@manuelnaranjo manuelnaranjo self-assigned this Mar 4, 2025
@manuelnaranjo manuelnaranjo force-pushed the mnaranjo/fix-circular-dependencies branch 4 times, most recently from 0f6989d to 8e05911 Compare March 7, 2025 12:19
def _rpm_impl(ctx):
if ctx.attr.urls:
downloaded_file_path = "downloaded"
downloaded_file_path = ctx.attr.urls[0].split("/")[-1]
Copy link
Collaborator

Choose a reason for hiding this comment

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

why do we need to change this?

packageNames := keys(allPackages)
sortedPackages := make([]*bazeldnf.RPM, 0, len(packageNames))
for _, name := range packageNames {
slices.Sort(targets)
Copy link
Collaborator

Choose a reason for hiding this comment

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

why do we care about the order of the targets?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

To make the lock file generation consistent and stable, we don't want multiple runs of the same required packages against the same db to be changing due to sorting differences

pkg.SetDependencies(deps)
pkg.SetDependencies(deps)
} else {
pkg.SetDependencies(nil)
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is presumably to make it so that packages that we didn't directly request reduce the fanout they drag in?

Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like we'll probably fail later rounds of collecting dependencies if we have a scenario like:
A -> B -> D
B -> D
C -> B -> D

In this scenario it (superficially) looks like we're going to to drop B's dependency on D before we process C.

It'd be good if we can separate out the traversal part here via something like https://github.com/dominikbraun/graph which will allow us to do a simple topological sort which we can then transform into the appropriate lockfile outputs.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think now this is fixed

@manuelnaranjo manuelnaranjo force-pushed the mnaranjo/fix-circular-dependencies branch from 8e05911 to 42d52c4 Compare March 19, 2025 08:55
Make sure only those packages that the lock file required contain the
full qualified list of dependencies, this allows to avoid circular
dependencies in bazel land
Now the lock file is in the expected state, make the repo config a bit
stricter to test even more functionality
Now that we have fixed the bug we can make this job actually fail
In lockfile mode we need to make sure that only those RPMs requested at
the time of the lockfile can be depend upon, otherwise we allow users
to consume from RPMs that may not have their entire dependency tree
populated.
Make sure the depsets are working as expected to avoid getting
duplicates in the final list of dependencies going into rpmtree
Make sure circular dependencies are resolved when rendering the lock
file, we flatten the dependency list of all requested packages.
When consuming the lock file make sure we only make use of the top level
dependencies ignoring transitive ones.
Make sure we have a test with a fake lock file that simulates a totally
circular dependency tree to validate that starlark behaves as expected
Make sure we let the user know to not use incompatible bazel versions,
as there are bugs that lead to issues like [this one](
    bazel-contrib/rules_jvm_external#1205
)
Make sure that when + is found in a package name and ignore_deps is set
to False, then the visible package name also gets + replaced with plus
@manuelnaranjo manuelnaranjo force-pushed the mnaranjo/fix-circular-dependencies branch from 42d52c4 to 4e0b823 Compare April 9, 2025 17:53
lock_file_json = json.decode(content)

if not config.ignore_deps:
if versions.is_at_least("7", versions.get()) and not versions.is_at_least("7.4.0", versions.get()):
Copy link
Collaborator

Choose a reason for hiding this comment

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

where are these constraints coming from?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't remember exactly why, what I do remember is setting the bazel version to 7.0 and so until 7.4.0 with bazelisk and it was failing with some weird error, same for 8.x

Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably worth digging into this a bit more. We're effectively saying that we no longer support pre 7.4 versions of bazel? That's a fairly strong assertion.

"integrity": attr.string(),
"dependencies": attr.label_list(
mandatory = False,
providers = [RpmInfo],
Copy link
Collaborator

Choose a reason for hiding this comment

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

why do we want to drop this?

"repository": []string{},
},
expectedRPMs: []*bazeldnf.RPM{
newSimpleRPM("package1", "package2", "package4"),
Copy link
Collaborator

Choose a reason for hiding this comment

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

why wouldn't package2 also have package4 in this case?

slices.Sort(targets)
sortedPackages := make([]*bazeldnf.RPM, 0, len(allPackages))

requested := make(map[string]bool)
Copy link
Collaborator

Choose a reason for hiding this comment

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

should use empty struct instead of bool here as the former uses zero bytes

for _, name := range sortedKeys(allPackages) {
pkg := allPackages[name]
deps, err := collectDependencies(name, pkg.Dependencies, providers, ignored)
if _, requested := requested[name]; !requested {
Copy link
Collaborator

Choose a reason for hiding this comment

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

should use ok here instead of requested because that's the normal pattern and this overloads the variable name

sortedPackages := make([]*bazeldnf.RPM, 0, len(packageNames))
for _, name := range packageNames {
slices.Sort(targets)
sortedPackages := make([]*bazeldnf.RPM, 0, len(allPackages))
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe move this down to where sortedPackages is constructed so it's a bit more clear what's happening

continue
}

pkg.SetDependencies(nil)
Copy link
Collaborator

Choose a reason for hiding this comment

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

why set dependencies to nil here?

}

slices.SortFunc(sortedPackages, func(a, b *bazeldnf.RPM) int {
return cmp.Compare(a.Name, b.Name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

isn't the sorting already implicit by virtue of having constructed sortedPackages with the sorted keys?

}

func collectDependencies(pkg string, requires []string, providers map[string]string, ignored map[string]bool) ([]string, error) {
func collectDependencies(pkg string, requires []string, providers map[string]string, ignored map[string]bool, allPackages map[string]*bazeldnf.RPM) ([]string, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

can we use something like https://pkg.go.dev/github.com/dominikbraun/graph to do a topological sort of the dependencies here and just get the result of that?

depSet := make(map[string]bool)
for _, req := range requires {
explored := make(map[string]bool)
for len(requires) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we just use something like https://pkg.go.dev/github.com/dominikbraun/graph and do a topological sort?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants