Hey I’ve been trying to read about creating custom...
# general
c
Hey I’ve been trying to read about creating custom targets in pants. What I’m trying to achieve is create a
python_binary
target that can remove dependencies or transitive dependencies and not packaged them. I’ve got some util targets that depend on
tensorflow
, but don’t use it the code paths in certain binaries. I know we could be disciplined and not have it in the build graph but that could add a lot of targets that aren’t really necessary. I’ve used bazel’s
depset
in before achieving something like this: Is there a good way to get this in pants?
h
Hey Charlie. With the upcoming Pants 2.0, we added
!
to the
dependencies
field as a way to ignore dependencies from a target. The simplest thing to do would be to write a macro (https://www.pantsbuild.org/v2.0/docs/macros) like
custom_python_binary
and have it always add
'!//:tensorflow'
to the
dependencies
field. —
or transitive dependencies
This is a little trickier. In Pants, creating a custom target type and/or using a macro would only influence the direct dependencies for that target. That is, it would only help to avoid this case:
Copy code
python_binary(
  dependencies=["bad_dep"]
)
Not this case:
Copy code
python_library(
  name="util",
  dependencies=["bad_dep"],
)

python_binary(
  dependencies=[":util"],
)
-- But, you could use the Rules API to essentially fork the builtin
binary
implementation. You could do something like check “If the target is a
custom_python_binary
, then no matter what exclude tensorflow; otherwise, use dependencies like normal.” You’d use the builtin rules for everything else like
test
, and swap in your rules that you forked. It sounds like correctly handling transitive deps is important too, right?
c
Yes It’s important for transitive too. How are transitive deps resolved? I was hoping I could extend the
python_binary
implementation with another field
deps_to_remove
or something.
👍 1
I was hoping I could extend the implementation and just remove everything in
deps_to_remove
.
h
Another approach is to have two targets referring to the same util code. One with tensorflow, and one without:
Copy code
python_library(
  name="utils_without_tf",
  dependencies=["foo", "bar"]
)

python_library(
  name="utils",
  dependencies=[":utils_without_tf", "//:tensorflow"]
)
But this means that the BUILD file authors must now go out of their way to depend on the correct target. And generally Pants is less ergonomic when there’s >1 target owning a file. For example, dependency inference wouldn’t be able to infer a dependency anymore because it doesn’t know which of the two targets you want to use.
How are transitive deps resolved?
Transitive deps are resolved through the Rules API. In call sites that need transitive deps, they say
await Get(Targets, TransitiveTargets([input_address1, input_address2])
(https://www.pantsbuild.org/v2.0/docs/rules-api-and-target-api#transitive-dependencies-with-transitivetargets) In your plugin / fork of the
binary
goal, you could filter that out to exclude TF when relevant.
remove everything in deps_to_remove
Yeah, at the moment
deps_to_remove
(via
!
ignores) only applies to your direct deps, not your transitive deps. Possibly, we could add that mechanism - I’m curious if other would find it useful too.
I’d be happy to help you write a plugin if you’re interested / send you the snippet to use. There wouldn’t be much code involved. The main annoyance would be keeping up to date with new Pants releases.
c
That would be great.
I haven’t gone through enough of the v2 docs. When do you think 2.0.0 will be marked stable?
h
We’re hoping to have alpha0 tomorrow or Friday. There’s a pretty bad performance regression in the time to create the rule graph during the past 3 releases. We’re very close to landing a fix for.
Possibly, we could add that mechanism - I’m curious if other would find it useful too.
If you’re willing, might be helpful to open an issue with your feature request to be able to exclude transitive deps. https://github.com/pantsbuild/pants/issues/new That way other people could add if they’d find it useful too.
c
I think it could definitely be useful for other people. I’ll open an issue
❤️ 2
h
Thanks! One question that would be helpful for us to scope it: Would you want this to be expressed in the BUILD file, through the
dependencies
field? Or it’s okay to have more ceremony like having to define a new target type? If we want it to live in BUILD files, one possible syntax is to use
!!ignore_me_transitively
..? vs.
!ignore_me_directly
.
c
If the precedent is set
!
can ignore things it could be in the
BUILD
files. My concern is that could be too magical and a user wouldn’t understand what is going on. If there is a easy code snip-it you could put on the website it could be helpful to teach people about more advanced api. I don’t have a strong opinion.
h
If we add an entry point in the Target API, that would be pretty easy for a codebase admin to do. The code would essentially be:
Copy code
class CustomPythonBinaryDependencies(Dependencies):
   exclude_transitively = ["bad_target"]

class CustomPythonBinary(Target):
  alias = "custom_python_binary"
  core_fields = (*(FrozenOrderedSet(PythonBinary.core_fields) - {Dependencies}), CustomPythonBinaryDependencies)
Then, anytime the BUILD file uses
custom_python_binary
, those hardcoded excluded targets would be ignored. Other than that, it behaves identically to a normal
python_binary
target. But that’s really really special-cased, and not at all obvious where that would fit into the docs. It also might not be generic enough. It requires you to put a list of hardcoded addresses in a target definition, rather than being able to tweak in a decentralized way in a BUILD file.
I’m think that we will either want to a) not support this mechanism officially because it’s too much of a special case / not wanted broadly enough, or b) add the
!!
syntax and very lightly document it
h
I can see this being useful for other users, so we'd definitely welcome the contribution. There are complications though. E.g., if we go the
!!
route, does that mean "don't bring in this dep through this target"? Or "don't bring in this dep through any target'? Maybe the
!!
has to be on
python_binary
or other root targets, and it means "strip this dep from the transitive closure when computed on (but not through) this target"?
h
Agreed we’d need to answer that question. Expecting it to be on a “root” target—i.e. a target with no dependees—is tricky to implement, though. There is nothing enforcing that no one depends on a
python_tests
or
python_binary
target, outside of convention. The best thing I could think of to try our best to enforce that
!!
is only used on roots is to have plugin authors express if it’s supported in the Target API. When defining a Dependencies field for their target type, they could set a class property `supports_transitive_excludes = True`:
Copy code
class PythonBinaryDependencies(Dependencies):
     supports_transitive_excludes = True
If this is set to the default of
False
, then we’ll error if any values in the
dependencies
field start with
!!
.
I’ll be working on this issue today 🙂 I realized last night that it wouldn’t be as simple to “swap-in” your custom rule for
binary
than I hoped. And we think other users indeed may find this useful. @clean-night-52582 if you have a moment, could you please ack if https://github.com/pantsbuild/pants/issues/10661#issuecomment-677793120 sounds on the right track?
c
Sounds great. Commented that on the issue
h
Great! I’m hoping to get it into the alpha0 release we’re planning for tomorrow. So far no hiccups in implementing it, beyond getting pulled to another priority
c
That’s awesome!
h