facebook / buck2

Build system, successor to Buck
https://buck2.build/
Apache License 2.0
3.53k stars 215 forks source link

aquery function confusion (deps vs all_actions) #511

Open zjturner opened 10 months ago

zjturner commented 10 months ago

I wanted to do something that I thought was pretty simple. Get all the actions for a particular target. I tried all_actions(), and for a while I thought that was doing the right thing.

Later I came to find out that this would include actions that, I guess, aren't normally run when you build the target. I came to this conclusion after noticing that when I run buck2 aquery all_actions(<some-target-literal>) I will occasionally see both PIC and non-PIC entries in the output for the exact same file. I know this isn't correct, because when I just build the target buck2 build <the-same-target-literal>, no actions execute which build the PIC version.

So I started playing around with other query functions. I tried buck2 aquery deps(<some-target-literal>, 1) and found that this more closely matches the real set of actions that run when you build the target. But that returns a bunch of dependent targets that I didn't want. Like toolchain targets, and other actions for targets that aren't that exact same target literal.

Then I found that I can use filter(<target>, deps(<target>, 1)) to get the exact set that I wanted.

But that didn't work either because that was actually missing some targets that were needed. For example, it didn't include the action for writing the .cpp.argsfile for an exe target, whereas all_actions() did.

So now I'm back to being stuck again.

Ultimately, what I'm really interested in the following: For a given target literal, I want

  1. All c_compile and cxx_compile actions.
  2. All write actions that output argsfiles referenced by any action from 1.

Is this possible?

Separately, what is the reason for all_actions() seemingly including actions that don't execute when you buck2 build the same target? And why is all_actions(T) a superset of filter(T, deps(T, 1)) instead of being identical?

wendy728 commented 9 months ago

All c_compile and cxx_compile actions.

Maybe attrfilter would work? (These are buck1 docs, we are working on adding this to buck2 docs, but the concept/args are the same). You could filter the results based on the category attr matching c_compile or cxx_compile.

All write actions that output argsfiles referenced by any action from 1.

We don't support outputs attr on actions, and we aren't planning to. I think maybe you can also use attrfilter on cmd attr to search for the expected file extension, but maybe this is brittle. cc @cjhopman for any other ideas

Separately, what is the reason for all_actions() seemingly including actions that don't execute when you buck2 build the same target? And why is all_actions(T) a superset of filter(T, deps(T, 1)) instead of being identical?

I think all_actions() is for obtaining all the registered actions declared within the analysis of a given target, which I think will end up including both pic and non pic actions. deps() operates just on the action's inputs, so I think it doesn't necessarily include all of the actions that all_actions() would find, since the target literal might have declared other actions.

cc @cjhopman in case the above explanation is incorrect, or there's more to add

zjturner commented 9 months ago

I also found in the prelude where pic actions are added unconditionally. Why is this? If nothing ever consumes pic objects, what’s the point of creating actions to generate them?

ndmitchell commented 9 months ago

When we do analysis for a C++ library we produce a static, shared and pic version. All three of those are available as subtargets, e.g. buck2 build :foo[shared]. But they are also available in the providers, so that depending on how the consumer (e.g. the C++ binary) wants they can grab the shared, static or pic versions. We only build the actions that are actually required for the final DefaultInfo.

So to answer the question we create the actions since we don't know when we create them if someone will want them or not. We only run the actions if they are actually required. With things like anon_targets we can actually defer creating the actions until they are required, at the cost of other complexity. We don't bother for C++ libraries (only three variations, most of the action cost is in the compilation rather than the link) but we do for Swift libraries (they can be infinite variations).