Skip to content

Handle files from external repos (fix breaking change in Bazel 3.7.0) #2138

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

Merged
merged 20 commits into from
Sep 9, 2023

Conversation

jgsogo
Copy link
Contributor

@jgsogo jgsogo commented Aug 29, 2023

Fixes #1780

In Bazel 3.7.0, the path returned by build_file_path changed (bazelbuild/bazel#12344), it no longer contains the external/<repo> prefix for external files. This breaks the expectations of this rule in the paths.relativize function call.

In this PR I prepend the workspace_root (external/<repo>) to build_file_path (<path/in/the/repo>), and use path (external/<repo>/path/in/the/repo) instead of short_path (../<repo>/path/in/the/repo).

For files that are generated in different configurations I need to strip the root which contains the bazel-out/darwin-fastbuild-ST-a1a0f4088294/bin/ that will never appear in the package_root path.

Note.- I will probably need some help to add tests for this change I've tried to add one test

@jgsogo jgsogo marked this pull request as ready for review August 29, 2023 09:42
@jgsogo

This comment was marked as resolved.

@jgsogo jgsogo changed the title Consider workspace_root in package_root Handle files from external repos (fix breaking change in Bazel 3.7.0) Aug 29, 2023
Copy link
Collaborator

@UebelAndre UebelAndre left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! Just one question 😄


if crate_root and (crate_root.is_source or crate_root.root.path != ctx.bin_dir.path):
generated_root = ctx.actions.declare_file(paths.relativize(crate_root.short_path, package_root))
crate_root_path = paths.relativize(crate_root.path, crate_root.root.path)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would it be simpler to just check if the path starts with ../ instead?

Suggested change
crate_root_path = paths.relativize(crate_root.path, crate_root.root.path)
if crate_root.short_path.startswith("../"):
generated_root = ctx.actions.declare_file(paths.relativize("external/" + crate_root.short_path[len("../"):], package_root))
else:
generated_root = ctx.actions.declare_file(paths.relativize(crate_root.short_path, package_root))

Copy link
Contributor Author

@jgsogo jgsogo Aug 31, 2023

Choose a reason for hiding this comment

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

Honestly, I find my approach easier to read without the if/else and no hardcoded strings (very unlikely, but they might change in the future). Maybe I can rename the variable crate_root_path to crate_root_rel_path to make it a bit more explicit?

In any case, no strong opinion here. If you feel like your approach is easier to read and maintain, I'm fine. You have more Bazel experience than me.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think explicitly handling ../ vs external/ is easier for me to maintain but would not broadly say it's more maintainable. As it's written now I feels like there's a lot going on. With either change though I think there should be a comment explaining what's happening but I do kinda like the simplicity of targeting the specific failure. If you want to keep your approach could you break it out into another function with an explanation of what's going on?


if crate_root and (crate_root.is_source or crate_root.root.path != ctx.bin_dir.path):
generated_root = ctx.actions.declare_file(paths.relativize(crate_root.short_path, package_root))
crate_root_path = paths.relativize(crate_root.path, crate_root.root.path)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think explicitly handling ../ vs external/ is easier for me to maintain but would not broadly say it's more maintainable. As it's written now I feels like there's a lot going on. With either change though I think there should be a comment explaining what's happening but I do kinda like the simplicity of targeting the specific failure. If you want to keep your approach could you break it out into another function with an explanation of what's going on?

@jgsogo
Copy link
Contributor Author

jgsogo commented Sep 4, 2023

I moved the logic to another function and added some description to it with a (hopefully) good enough comment. Thanks a lot for the reviews!

Copy link
Collaborator

@UebelAndre UebelAndre left a comment

Choose a reason for hiding this comment

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

I can take another look later today but I’m wondering why this regression wasn’t caught. Is this fixing behavior for Bazel 3? Is there some kind of regression test that can be added as part of this PR?

@jgsogo
Copy link
Contributor Author

jgsogo commented Sep 5, 2023

I can take another look later today but I’m wondering why this regression wasn’t caught. Is this fixing behavior for Bazel 3? Is there some kind of regression test that can be added as part of this PR?

I think this has never been tested. The breaking change was reported in 2020 (bazelbuild/bazel#12344), and it looks like there are not so many people using rules_rust to consume Rust targets from external repositories that involve generated files 🤷

In order to test this we would need to use an external repository. I don't have enough Bazel knowledge to know if this can be reproduced without actually using another repository (maybe using another WORKSPACE in this same repo?)

@scentini
Copy link
Collaborator

scentini commented Sep 5, 2023

Yeah, I introduced the code initially, and coming from a monorepo, external dependencies didn't come to mind.

There is no way to test it without external dependencies, however we have examples of using external dependencies under //test/cc_common_link and test/cc_common_link/with_global_alloc

@jgsogo
Copy link
Contributor Author

jgsogo commented Sep 6, 2023

Thanks for the pointers, @scentini . I've added one basic test to validate the new implementation (I've checked it fails with previous logic).

if src_file.is_source or src_file.root.path != ctx.bin_dir.path:
src_short_path = paths.relativize(src_file.path, src_file.root.path)
src_symlink = ctx.actions.declare_file(paths.relativize(src_short_path, package_root))
ctx.actions.symlink(
Copy link
Collaborator

Choose a reason for hiding this comment

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

Small nit, can you put the action to symlink back in _transform_sources? The thing I wanted to make crystal clear is what is happening with the path manipulation here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Honestly I'm not sure about it, there will be more duplicated logic and more lines. As a newcomer, I find it harder to read (I was not used to the previous implementation).

Maybe I'm missing something and you have a much better proposal. I will work on the comment about the tests and come back here later to think about something better/easier.


def _symlink_for_non_generated_source(ctx, src_file, package_root):
    if src_file.is_source or src_file.root.path != ctx.bin_dir.path:
        src_short_path = paths.relativize(src_file.path, src_file.root.path)
        src_symlink = ctx.actions.declare_file(paths.relativize(src_short_path, package_root))
        return src_symlink
    else:
        return None
        
def _transform_sources(ctx, srcs, crate_root):
    has_generated_sources = len([src for src in srcs if not src.is_source]) > 0

    if not has_generated_sources:
        return srcs, crate_root

    package_root = paths.dirname(paths.join(ctx.label.workspace_root, ctx.build_file_path))
    generated_sources = []
    for src_file in srcs:
        if src_file == crate_root:
            continue
        src_symlink = _symlink_for_non_generated_source(ctx, src_file, package_root)
        if src_symlink:
            ctx.actions.symlink(
                output = src_symlink,
                target_file = src_file,
                progress_message = "Creating symlink to source file: {}".format(src_file.path),
            )
            generated_sources.append(src_symlink)
        else:
            generated_sources.append(src_file)
            
    generated_root = crate_root
    if crate_root:
        src_symlink = _symlink_for_non_generated_source(ctx, crate_root, package_root)
        if src_symlink:
            ctx.actions.symlink(
                output = src_symlink,
                target_file = crate_root,
                progress_message = "Creating symlink to source file: {}".format(src_file.path),
            )
            generated_sources.append(src_symlink)
        else:
            generated_sources.append(crate_root)

    return generated_sources, generated_root

@@ -575,6 +575,14 @@ tasks:
test_flags:
- "--@rules_rust//rust/toolchain/channel=nightly"
- "--@rules_rust//:no_std=alloc"
generated_inputs_external_repo_ubuntu2004:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of adding a new job, could you not instead make a repository rule that writes a small target to an external repo and add it to https://github.com/bazelbuild/rules_rust/blob/0.27.0/test/deps.bzl ?

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 think I did the right thing here 8d0b467.

FYI. If the rust library is created in the root of the repository, then current implementation doesn't fail.

@jgsogo jgsogo force-pushed the fix/1780-external-repo branch from d0b6281 to 923ac33 Compare September 7, 2023 09:35
Copy link
Collaborator

@UebelAndre UebelAndre left a comment

Choose a reason for hiding this comment

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

Awesome work, thank you so much!

@UebelAndre UebelAndre merged commit 1b19a46 into bazelbuild:main Sep 9, 2023
@jgsogo jgsogo deleted the fix/1780-external-repo branch September 11, 2023 06:38
@jgsogo
Copy link
Contributor Author

jgsogo commented Sep 11, 2023

It's been a pleasure. Looking forward to the next release. Thanks for the hard work on this project 🙌

ttiurani pushed a commit to ttiurani/rules_rust that referenced this pull request Sep 15, 2023
…bazelbuild#2138)

Fixes bazelbuild#1780

In Bazel 3.7.0, the path returned by `build_file_path` changed
(bazelbuild/bazel#12344), it no longer
contains the `external/<repo>` prefix for external files. This breaks
the expectations of this rule in the [`paths.relativize`
function](https://github.com/bazelbuild/bazel-skylib/blob/main/docs/paths_doc.md#pathsrelativize)
call.

In this PR I prepend the `workspace_root` (`external/<repo>`) to
`build_file_path` (`<path/in/the/repo>`), and use `path`
(`external/<repo>/path/in/the/repo`) instead of `short_path`
(`../<repo>/path/in/the/repo`).

For files that are generated in different configurations I need to strip
the `root` which contains the
`bazel-out/darwin-fastbuild-ST-a1a0f4088294/bin/` that will never appear
in the `package_root` path.

~Note.- I will probably need some help to add tests for this change~
I've tried to add one test

---------

Co-authored-by: UebelAndre <[email protected]>
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.

Build fail when depending on a library in a different repo that mixes generated and non-generated sources
3 participants