@@ -226,13 +226,6 @@ def _create_repository_execution_environment(rctx):
226226
227227 return env
228228
229- _BUILD_FILE_CONTENTS = """\
230- package(default_visibility = ["//visibility:public"])
231-
232- # Ensure the `requirements.bzl` source can be accessed by stardoc, since users load() from it
233- exports_files(["requirements.bzl"])
234- """
235-
236229def locked_requirements_label (ctx , attr ):
237230 """Get the preferred label for a locked requirements file based on platform.
238231
@@ -360,15 +353,16 @@ def _pip_repository_bzlmod_impl(rctx):
360353
361354 repo_name = rctx .attr .name .split ("~" )[- 1 ]
362355
363- build_contents = _BUILD_FILE_CONTENTS
364-
365356 if rctx .attr .incompatible_generate_aliases :
366357 _pkg_aliases (rctx , repo_name , bzl_packages )
358+ build_footer = ""
367359 else :
368- build_contents + = _bzlmod_pkg_aliases (repo_name , bzl_packages )
360+ build_footer = _bzlmod_pkg_aliases (repo_name , bzl_packages )
369361
370- rctx .file ("BUILD.bazel" , build_contents )
371- rctx .template ("requirements.bzl" , rctx .attr ._template , substitutions = {
362+ rctx .file ("BUILD.bazel" , rctx .attr ._build_template , substitutions = {
363+ "%%FOOTER%%" : build_footer ,
364+ })
365+ rctx .template ("requirements.bzl" , rctx .attr ._requirements_template , substitutions = {
372366 "%%ALL_REQUIREMENTS%%" : _format_repr_list ([
373367 "@{}//{}" .format (repo_name , p ) if rctx .attr .incompatible_generate_aliases else "@{}_{}//:pkg" .format (rctx .attr .name , p )
374368 for p in bzl_packages
@@ -406,9 +400,12 @@ wheels are fetched/built only for the targets specified by 'build/run/test'.
406400 allow_single_file = True ,
407401 doc = "Override the requirements_lock attribute when the host platform is Windows" ,
408402 ),
409- "_template " : attr .label (
403+ "_requirements_template " : attr .label (
410404 default = ":pip_repository_requirements_bzlmod.bzl.tmpl" ,
411405 ),
406+ "_build_template" : attr .label (
407+ default = ":pip_repository_build.bazel.tmpl" ,
408+ ),
412409}
413410
414411pip_repository_bzlmod = repository_rule (
@@ -422,11 +419,46 @@ def _pip_repository_impl(rctx):
422419 content = rctx .read (requirements_txt )
423420 parsed_requirements_txt = parse_requirements (content )
424421
425- packages = [(_clean_pkg_name (name ), requirement ) for name , requirement in parsed_requirements_txt .requirements ]
422+ # Apply name normalizations to the composite libs def once
423+ composite_libs = {
424+ _clean_pkg_name (name ): [_clean_pkg_name (it ) for it in components ]
425+ for name , components in rctx .attr .composite_libs .items ()
426+ }
426427
427- bzl_packages = sorted ([name for name , _ in packages ])
428+ # Ditto for requirements defs
429+ requirements = {
430+ _clean_pkg_name (name ): requirement
431+ for name , requirement in parsed_requirements_txt .requirements
432+ }
433+
434+ # Map normalized package names to a composite
435+ composite_mapping = {
436+ name : composite_name
437+ for composite_name , names in composite_libs .items ()
438+ for name in names
439+ }
440+
441+ # Normal packages are defined by a single requirement.
442+ # We will deal with composites shortly.
443+ normal_packages = [
444+ (name , requirement )
445+ for name , requirement in requirements .items ()
446+ if name not in composite_mapping
447+ ]
448+
449+ # Composite packages are a cluster which can only be depended on together
450+ composite_packages = {
451+ _clean_pkg_name (composite_name ): [
452+ (rname , requirements [rname ])
453+ for rname in composite_components
454+ ]
455+ for composite_name , composite_components in rctx .attr .composite_libs .items ()
456+ }
457+
458+ bzl_packages = sorted ([name for name , _ in requirements .items ()])
428459
429460 imports = [
461+ 'load("@rules_python//python:defs.bzl", "py_library")' ,
430462 'load("@rules_python//python/pip_install:pip_repository.bzl", "whl_library")' ,
431463 ]
432464
@@ -463,8 +495,14 @@ def _pip_repository_impl(rctx):
463495 if rctx .attr .incompatible_generate_aliases :
464496 _pkg_aliases (rctx , rctx .attr .name , bzl_packages )
465497
466- rctx .file ("BUILD.bazel" , _BUILD_FILE_CONTENTS )
467- rctx .template ("requirements.bzl" , rctx .attr ._template , substitutions = {
498+ rctx .template ("lib.bzl" , rctx .attr ._lib_template , substitutions = {
499+ "%%NAME%%" : rctx .attr .name ,
500+ })
501+ rctx .template ("BUILD.bazel" , rctx .attr ._build_template , substitutions = {
502+ "%%NAME%%" : rctx .attr .name ,
503+ "%%FOOTER%%" : "" ,
504+ })
505+ rctx .template ("requirements.bzl" , rctx .attr ._requirements_template , substitutions = {
468506 "%%ALL_REQUIREMENTS%%" : _format_repr_list ([
469507 "@{}//{}" .format (rctx .attr .name , p ) if rctx .attr .incompatible_generate_aliases else "@{}_{}//:pkg" .format (rctx .attr .name , p )
470508 for p in bzl_packages
@@ -475,13 +513,15 @@ def _pip_repository_impl(rctx):
475513 ]),
476514 "%%ANNOTATIONS%%" : _format_dict (_repr_dict (annotations )),
477515 "%%CONFIG%%" : _format_dict (_repr_dict (config )),
516+ "%%CLUSTERS%%" : _format_dict (_repr_dict (composite_packages )),
517+ "%%CLUSTER_MAPPINGS%%" : _format_dict (_repr_dict (composite_mapping )),
478518 "%%EXTRA_PIP_ARGS%%" : json .encode (options ),
479519 "%%IMPORTS%%" : "\n " .join (sorted (imports )),
480520 "%%NAME%%" : rctx .attr .name ,
481521 "%%PACKAGES%%" : _format_repr_list (
482522 [
483523 ("{}_{}" .format (rctx .attr .name , p ), r )
484- for p , r in packages
524+ for p , r in normal_packages
485525 ],
486526 ),
487527 "%%REQUIREMENTS_LOCK%%" : str (requirements_txt ),
@@ -602,9 +642,18 @@ wheels are fetched/built only for the targets specified by 'build/run/test'.
602642 allow_single_file = True ,
603643 doc = "Override the requirements_lock attribute when the host platform is Windows" ,
604644 ),
605- "_template" : attr .label (
645+ "composite_libs" : attr .string_list_dict (
646+ doc = "Groups of requirements which represent dependency cycles and must be treated as composites." ,
647+ ),
648+ "_requirements_template" : attr .label (
606649 default = ":pip_repository_requirements.bzl.tmpl" ,
607650 ),
651+ "_build_template" : attr .label (
652+ default = ":pip_repository_build.bazel.tmpl" ,
653+ ),
654+ "_lib_template" : attr .label (
655+ default = ":pip_repository_lib.bzl.tmpl" ,
656+ ),
608657}
609658
610659pip_repository_attrs .update (** common_attrs )
@@ -673,6 +722,8 @@ def _whl_library_impl(rctx):
673722 "--annotation" ,
674723 rctx .path (rctx .attr .annotation ),
675724 ])
725+ for d in rctx .attr .skip_deps :
726+ args .extend (["--skip" , d ])
676727
677728 args = _parse_optional_attrs (rctx , args )
678729
@@ -705,6 +756,10 @@ whl_library_attrs = {
705756 mandatory = True ,
706757 doc = "Python requirement string describing the package to make available" ,
707758 ),
759+ "skip_deps" : attr .string_list (
760+ doc = "List of requirements to skip due to clustering" ,
761+ default = [],
762+ ),
708763}
709764
710765whl_library_attrs .update (** common_attrs )
0 commit comments