1414
1515""
1616
17+ load ("@bazel_tools//tools/build_defs/repo:utils.bzl" , "maybe" )
1718load ("//python/private:auth.bzl" , "AUTH_ATTRS" , "get_auth" )
1819load ("//python/private:envsubst.bzl" , "envsubst" )
1920load ("//python/private:is_standalone_interpreter.bzl" , "is_standalone_interpreter" )
@@ -28,6 +29,7 @@ load(":pypi_repo_utils.bzl", "pypi_repo_utils")
2829load (":urllib.bzl" , "urllib" )
2930load (":whl_extract.bzl" , "whl_extract" )
3031load (":whl_metadata.bzl" , "parse_entry_points" , "whl_metadata" )
32+ load (":whl_repo_name.bzl" , "whl_repo_name" )
3133
3234_CPPFLAGS = "CPPFLAGS"
3335_COMMAND_LINE_TOOLS_PATH_SLUG = "commandlinetools"
@@ -736,7 +738,7 @@ def _whl_deps_library_impl(rctx):
736738 repo = rctx .attr .repo or (
737739 str (rctx .attr .metadata_file ) if rctx .attr .metadata_file else None
738740 ),
739- extras = requirement ( rctx .attr . requirement ) .extras ,
741+ extras = rctx .attr .extras ,
740742 )
741743 rctx .file ("BUILD.bazel" , build_file_contents )
742744
@@ -748,9 +750,10 @@ whl_deps_library = repository_rule(
748750 "dep_template" ,
749751 "group_deps" ,
750752 "group_name" ,
751- "requirement " ,
753+ "repo_prefix " ,
752754 ]
753755 } | {
756+ "extras" : attr .string_list (doc = "The extras to configure for this repo." ),
754757 "metadata" : attr .string (
755758 doc = """
756759The subset of the METADATA contents that is needed for generation of the dependencies.
@@ -773,7 +776,7 @@ Does not depend on any python.
773776 environ = [REPO_DEBUG_ENV_VAR ],
774777)
775778
776- def whl_library (name , repo = None , ** kwargs ):
779+ def whl_library (name , repo = None , maybe = maybe , ** kwargs ):
777780 """Create a whl_library.
778781
779782 This proxies to one of the underlying implementations:
@@ -783,6 +786,9 @@ def whl_library(name, repo = None, **kwargs):
783786 Args:
784787 name: {type}`str` The name of the repo.
785788 repo: Unused, will be dropped in the next major release.
789+ maybe: This is the repo rule that is used in WORKSPACE mode and in the extension eval to
790+ dedupe some of the invocations. This has to be overridden on bzlmod using
791+ {obj}`repo_utils.bzlmod_maybe`.
786792 **kwargs: The args passed to the underlying implementation.
787793
788794 Returns:
@@ -794,6 +800,80 @@ def whl_library(name, repo = None, **kwargs):
794800 urls = kwargs .get ("urls" , [])
795801 filename = kwargs .get ("filename" )
796802 if whl_file or (urls and filename and filename .endswith (".whl" )):
797- whl_archive (name = name , ** kwargs )
803+ filename = filename or Label (whl_file ).name
804+ if "annotation" in kwargs or "whl_patches" in kwargs :
805+ # No reuse of the whl_library because there is an annotation here
806+ whl_archive (name = name , ** kwargs )
807+ return
808+
809+ extract_args = {
810+ k : v
811+ for k , v in kwargs .items ()
812+ if k not in {
813+ # TODO @aignas 2026-08-01: what about python_interpreter and python_interpreter_target
814+ "config_load" : None ,
815+ "dep_template" : None ,
816+ }
817+ }
818+ if "index_url" in extract_args :
819+ # TODO @aignas 2026-08-01: figure out where we should do the fix here.
820+ extract_args ["index_url" ] = extract_args ["index_url" ].strip ("/" )
821+
822+ # The extras do not affect the extraction, so normalize the requirement
823+ # to allow the same wheel to be extracted only once.
824+ extract_args ["requirement" ] = _without_extras (extract_args ["requirement" ])
825+ extract_repo_name = "w_{}" .format (
826+ whl_repo_name (filename , kwargs .get ("sha256" )),
827+ )
828+
829+ maybe (
830+ whl_archive ,
831+ name = extract_repo_name ,
832+ ** extract_args
833+ )
834+
835+ req = requirement (kwargs ["requirement" ])
836+
837+ deps_args = {
838+ k : kwargs .get (k )
839+ for k in [
840+ "config_load" ,
841+ "dep_template" ,
842+ "group_deps" ,
843+ "group_name" ,
844+ ]
845+ if kwargs .get (k ) != None
846+ } | {
847+ # TODO @aignas 2026-08-01: add extras only if the list is non-empty
848+ "extras" : req .extras ,
849+ "metadata_file" : "@{}//:metadata.json" .format (extract_repo_name ),
850+ }
851+ whl_deps_library (
852+ name = name ,
853+ ** deps_args
854+ )
798855 else :
856+ # No reuse of the whl_library because we have args that force the extraction of the whl
857+ # in the hub context. If we have whl-only pipstar extraction, then we can reuse the
858+ # extracted sources.
799859 pip_archive (name = name , ** kwargs )
860+
861+ def _without_extras (requirement_line ):
862+ """Remove the extras from a requirement line.
863+
864+ The extras do not affect which wheel is downloaded and extracted, so they
865+ can be removed to allow the same wheel to be extracted only once even when
866+ it is referenced with different extras.
867+
868+ Args:
869+ requirement_line: {type}`str` the requirement line, e.g.
870+ `foo[bar]==1.0 --hash=sha256:...`.
871+
872+ Returns:
873+ The requirement line without the extras, e.g. `foo==1.0 --hash=sha256:...`.
874+ """
875+ name_and_version , _ , extras = requirement_line .partition ("[" )
876+ if not extras :
877+ return requirement_line
878+ _ , _ , rest = extras .partition ("]" )
879+ return name_and_version + rest
0 commit comments