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

Allow artifact -> artifact symlink actions from Starlark #10695

Closed
wants to merge 6 commits into from

Conversation

Yannic
Copy link
Contributor

@Yannic Yannic commented Feb 3, 2020

Updates #7514

@irengrig irengrig added the team-Rules-Server Issues for serverside rules included with Bazel label Feb 3, 2020
private void registerSymlinkAction(
Artifact output, Artifact input, String progressMessage) throws EvalException {
if (output.isSymlink()) {
throw Starlark.errorf("output of symlink action must be created by declare_file()");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: "symlinks pointing to artifacts must be created by declare_file()?"

If you want a dangling symlink, it has to created by declare_symlink(), doesn't it?

// TODO(yannic): This should be configurable.
String progressMessage = "Creating symlink " + outputArtifact.getRootRelativePathString();

if (target instanceof String) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@laurentlb , is this kind of behavior change based on the runtime type of the argument okay?

It seems to be reasonable on the surface, but there doesn't seem to be that many instances of this in our code and maybe explicitly specifying which one we want like actions.symlink(output, file=<file>) / actions.symlink(output, symlink=<string>) would be 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.

TBH, I actually like having actions.symlink(output, file=<file>) / actions.symlink(output, symlink=<string>) more than overloading target based on the runtime type.

Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, but I want this to be idiomatic, that's why I asked for @laurentlb 's opinion!

@brandjon
Copy link
Member

brandjon commented Feb 6, 2020

Some thoughts:

  1. My understanding is that all symlink functionality in Starlark is currently experimental. Let's not remove any experimental designations in this PR; that requires a separate discussion.

  2. It seems the API we're exposing to Starlark is that a symlink is a special kind of File, created by declare_symlink() instead of declare_file() or other means, and that symlink() requires a symlink File. This should be clearly documented. (I also think the wording around output could be made clearer, but we can iterate on documentation separately. Maybe the param should be renamed to link for symmetry with man 1 ln?)

  3. We seem to be in agreement that direct overloading on path string vs File is confusing, and that a nice alternative to fully splitting symlink() into two methods would be to pass in mutually exclusive keyword args. I like the names target_path=... and target_file=... for these params.

@laurentlb
Copy link
Contributor

overloading on path string vs File is confusing

I agree. Maybe we need to resolve the situation about the experimental dangling symlink feature. Do we want to support it? Is it going away?

@lberki
Copy link
Contributor

lberki commented Feb 7, 2020

We definitely want to support it in the long term. For now, however, the fully supported one is the artifact -> artifact symlink.

@Yannic
Copy link
Contributor Author

Yannic commented Feb 8, 2020

Some thoughts:

  1. My understanding is that all symlink functionality in Starlark is currently experimental. Let's not remove any experimental designations in this PR; that requires a separate discussion.

"Normal" symlink functionality (i.e. artifact -> artifact) is currently non-existent in Starlark, only unresolved symlinks are available behind --experimental_allow_unresolved_symlinks. I don't think guarding artifact -> artifact symlinks with a flag is necessary (and definitely not with --experimental_allow_unresolved_symlinks, that would be really confusing to users).

  1. It seems the API we're exposing to Starlark is that a symlink is a special kind of File, created by declare_symlink() instead of declare_file() or other means, and that symlink() requires a symlink File. This should be clearly documented. (I also think the wording around output could be made clearer, but we can iterate on documentation separately. Maybe the param should be renamed to link for symmetry with man 1 ln?)

See 1. for declare_file() vs declare_symlink(). I agree that documentation should be improved as part of this PR :).

  1. We seem to be in agreement that direct overloading on path string vs File is confusing, and that a nice alternative to fully splitting symlink() into two methods would be to pass in mutually exclusive keyword args. I like the names target_path=... and target_file=... for these params.

Seems like we all agree that overloading is not the way to go.

@lberki
Copy link
Contributor

lberki commented Feb 10, 2020

Let's go with target_path= and target_file= then. ctx.actions.symlink() is nice enough that I don't want to uglify it and rename it to something like symlink_file().

I just realized that this is inconsistent with the symlink function in repository contexts which have from= and to= attributes, but given that those are very confusing (in fact, I was sure that from denotes the symlink and to denotes the target until I read the documentation), so I'd much rather keep the output/target names, unless @laurentlb or @brandjon strongly disagree.

@brandjon
Copy link
Member

"Normal" symlink functionality (i.e. artifact -> artifact) is currently non-existent in Starlark, only unresolved symlinks are available behind --experimental_allow_unresolved_symlinks. I don't think guarding artifact -> artifact symlinks with a flag is necessary

If it's experimental it needs to be behind a flag.

I'd much rather keep the output/target names

Any thoughts on link instead of output? I don't have a strong opinion, and output might be better after all if we're playing up the symmetry between normal file artifacts and symlink artifacts in Starlark API.

@Yannic Yannic force-pushed the symlink branch 3 times, most recently from 67efa42 to 7e3d5c9 Compare February 18, 2020 14:04
EOF

# bazel build //a:a && fail "build succeeded"
# [[ "$?" == 1 ]] || fail "unexpected exit code"
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 can't quite figure out why this isn't failing.
Looking at this comment, it seems like the action should fail.
Am I missing something here, or is that comment out-of date (or invalid for Bazel)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hehe. That's because Bazel makes every output file executable (which is not how I would like it to be, but it would be quite difficult to change now :( ). Probably this is where:

https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java#L737

If you want to test this functionality, symlink to a source file instead of one that was built by Bazel itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, but not exactly what I wanted to hear :/.

I'll use your workaround and symlink to a source file for the test.

@Yannic Yannic marked this pull request as ready for review February 18, 2020 14:28
@Yannic
Copy link
Contributor Author

Yannic commented Feb 18, 2020

This is ready for review now.

@brandjon I decided to stick with output to be consistent with ctx.actions.write, ctx.actions.run and ctx.actions.run_shell. I've also added an "experimental" warning to target_path (which is the only experimental part of this API).

Copy link
Contributor

@lberki lberki left a comment

Choose a reason for hiding this comment

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

Sorry for the delay. Feel free to nag me more in the future :)

EOF

# bazel build //a:a && fail "build succeeded"
# [[ "$?" == 1 ]] || fail "unexpected exit code"
Copy link
Contributor

Choose a reason for hiding this comment

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

Hehe. That's because Bazel makes every output file executable (which is not how I would like it to be, but it would be quite difficult to change now :( ). Probably this is where:

https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java#L737

If you want to test this functionality, symlink to a source file instead of one that was built by Bazel itself.

Copy link
Contributor Author

@Yannic Yannic left a comment

Choose a reason for hiding this comment

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

Thanks! ptal

EOF

# bazel build //a:a && fail "build succeeded"
# [[ "$?" == 1 ]] || fail "unexpected exit code"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, but not exactly what I wanted to hear :/.

I'll use your workaround and symlink to a source file for the test.

Copy link
Contributor

@lberki lberki left a comment

Choose a reason for hiding this comment

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

Looks reasonable. Let me see if this can still be imported.

@lberki
Copy link
Contributor

lberki commented Apr 1, 2020

Update: internal change is being reviewed by @brandjon

@Yannic
Copy link
Contributor Author

Yannic commented Apr 13, 2020

Any updates on this?

@brandjon
Copy link
Member

brandjon commented Apr 15, 2020

Hi Yannic, I had a few comments from the internal review, but first I want to step back and make sure I understand the big picture. I'm particularly cautious because we seem to have skipped a Starlark API review for this change and for lberki@'s earlier dangling symlink feature (which up until now has been marked experimental).

Questions for @lberki:

  1. There's an old doc, Symlinks in Bazel Remote Caching and Execution, that says dangling symlinks are explicitly not supported in remote execution. Add support for unresolved symbolic links #9075 and 2fc2e2b say different things about RBE compatibility but I don't know the nuances. What's the current status of this feature and remote execution, and does it block releasing any symlink functionality as non-experimental?

  2. To confirm your earlier post, you agree that the new functionality added in this PR is ok to be marked non-experimental, right?

For @Yannic and @lberki: I want to make sure we're on the same page. Does this describe the API?

  • declare_file() creates a regular artifact (isSymlink() == false). But the corresponding physical file may or may not be a symlink, depending on whether it was generated by symlink() or another action. So users would use declare_file() when they want something guaranteed to exist.

  • declare_symlink() creates a symlink artifact (isSymlink() == true). The corresponding physical file must be a symlink. It may or may not resolve to a path that exists, and that path may or may not be in the output tree. This functionality remains experimental for now.

I'll reply with my comments from the internal review shortly.

Copy link
Member

@brandjon brandjon left a comment

Choose a reason for hiding this comment

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

If we're distinguishing between artifacts created by declare_file and declare_symlink, do we have a way to expose the difference to users? Like a myfile.is_symlink bool (similar to is_source)?

Copy link
Contributor Author

@Yannic Yannic left a comment

Choose a reason for hiding this comment

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

I think you got the API right (modulo small caveat: symlink() with ``isSymlink() == false` may actually copy on Windows).

If we're distinguishing between artifacts created by declare_file and declare_symlink, do we have a way to expose the difference to users? Like a myfile.is_symlink bool (similar to is_source)?

.is_symlink isn't available on FileAPI yet, but it seems not too complicated to add it (Artifact already known whether it's a symlink or not). Should we add it as part of this change?

Copy link
Member

@brandjon brandjon left a comment

Choose a reason for hiding this comment

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

.is_symlink isn't available on FileAPI yet, but it seems not too complicated to add it (Artifact already known whether it's a symlink or not). Should we add it as part of this change?

No, this PR is already large enough and iterated enough times. :) Happy to accept this in a follow-up, or leave an open FR.

@brandjon
Copy link
Member

@lberki, when you get a chance please weigh in on these three concerns:

Copy link
Contributor Author

@Yannic Yannic left a comment

Choose a reason for hiding this comment

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

.is_symlink isn't available on FileAPI yet, but it seems not too complicated to add it (Artifact already known whether it's a symlink or not). Should we add it as part of this change?

No, this PR is already large enough and iterated enough times. :) Happy to accept this in a follow-up, or leave an open FR.

Filed #11209

Copy link
Member

@brandjon brandjon left a comment

Choose a reason for hiding this comment

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

LGTM, I'll get my answers from @lberki on the internal merge. Thanks!

@lberki
Copy link
Contributor

lberki commented Apr 27, 2020

re: #10695 (comment), we are on the same page.

  1. The reason why unresolved symlinks are marked as experimental is exactly the interaction with remote execution (maybe it works, but I certainly haven't tried). In addition, it hasn't gone through a proper design review. ctx.actions.symlink() itself for non-unresolved (double negation! say, regualr symlinks) is a pretty safe change, it only exposes old internal functionality that hasn't changed since forever.

  2. Yep. You may want to ask @laurentlb what he thinks about it, but I don't think he'd object.

@brandjon
Copy link
Member

Thanks, that should be everything then!

@brandjon
Copy link
Member

(In internal merge, I added a documentation note to target_path that it's experimental and requires setting the flag.)

@bazel-io bazel-io closed this in 43f793c Apr 30, 2020
Yannic added a commit to Yannic/bazel that referenced this pull request Apr 30, 2020
bazel-io pushed a commit that referenced this pull request Apr 30, 2020
Follow-up to #10695

Closes #11268.

PiperOrigin-RevId: 309291255
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes team-Rules-Server Issues for serverside rules included with Bazel
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants