@@ -296,7 +296,8 @@ def _raw_whl_repos(module_ctx, lock_specs):
296296 #
297297 # Assume (potentially a problem!)
298298 name = _whl_repo_name (package , whl )
299- print ("Creating whl repo" , name )
299+
300+ # print("Creating whl repo", name)
300301 downloaded_file_path = url .split ("/" )[- 1 ]
301302 spec = dict (
302303 name = name ,
@@ -331,7 +332,8 @@ def _sbuild_repos(module_ctx, lock_specs):
331332 continue
332333
333334 name = _sbuild_repo_name (hub_name , venv_name , package )
334- print ("Creating sdist repo" , name )
335+
336+ # print("Creating sdist repo", name)
335337 sdist_build (
336338 name = name ,
337339 src = "@" + _sdist_repo_name (package ) + "//file" ,
@@ -348,6 +350,90 @@ def _whl_install_repo_name(hub, venv, package):
348350 package ["name" ],
349351 )
350352
353+ def debug (it ):
354+ return "<%s %s>" % (type (it ), {
355+ k : getattr (it , k )
356+ for k in dir (it )
357+ })
358+
359+ def parse_ini (lines ):
360+ dict = {}
361+ heading = None
362+ for line in lines .split ("\n " ):
363+ line = line .strip ()
364+ if line .startswith ("[" ) and line .endswith ("]" ):
365+ heading = line [1 :- 2 ]
366+ dict [heading ] = {}
367+
368+ elif "=" in line and heading :
369+ key , value = line .split ("=" , 1 )
370+ key = key .strip ()
371+ value = value .strip ()
372+ dict [heading ][key ] = value
373+
374+ return dict
375+
376+ def _collect_entrypoints (module_ctx , lock_specs ):
377+ # Collect all the packages anyone's allowing entrypoints for
378+ packages_to_collect = {}
379+ for mod in module_ctx .modules :
380+ for it in mod .tags .discover_entrypoints :
381+ packages_to_collect [normalize_name (it .requirement )] = 1
382+
383+ entrypoints = {}
384+
385+ # Collect predeclared entrypoints
386+ for mod in module_ctx .modules :
387+ for it in mod .tags .declare_entrypoint :
388+ r = normalize_name (it .requirement )
389+ entrypoints .setdefault (r , {})
390+ entrypoints [r ][normalize_name (it .name )] = it .entrypoint
391+
392+ print (packages_to_collect )
393+
394+ for hub_name , venvs in lock_specs .items ():
395+ for venv_name , lock in venvs .items ():
396+ for package in lock .get ("package" , []):
397+ # We use packages_to_collect as both a set and a worklist
398+ if package ["name" ] not in packages_to_collect :
399+ continue
400+
401+ reference_prebuild = None
402+
403+ # Find the smallest available wheel
404+ for whl in package .get ("wheels" , []):
405+ reference_prebuild = whl
406+ break
407+
408+ if not reference_prebuild :
409+ continue
410+
411+ whl_filename = whl ["url" ].split ("/" )[- 1 ]
412+ file = "private/" + whl_filename
413+ module_ctx .download (reference_prebuild ["url" ], sha256 = reference_prebuild ["hash" ][len ("sha256:" ):], output = file )
414+ res = module_ctx .execute (
415+ [
416+ "tar" , # FIXME: Use a hermetic tar here somehow?
417+ "-xOzf" ,
418+ file ,
419+ "*.dist-info/entry_points.txt" ,
420+ ],
421+ )
422+ print (debug (res ))
423+ if res .return_code == 0 :
424+ entrypoints .setdefault (package ["name" ], {})
425+ whl_entrypoints = parse_ini (res .stdout )
426+ print (package ["name" ], res .stdout , whl_entrypoints )
427+ for name , entrypoint in whl_entrypoints .get ("console_script" , {}).items ():
428+ entrypoints [package ["name" ]][normalize_name (name )] = entrypoint
429+
430+ packages_to_collect .pop (package ["name" ])
431+
432+ else :
433+ print (file , res .exit_code , res .stderr )
434+
435+ return entrypoints
436+
351437def _whl_install_repos (module_ctx , lock_specs ):
352438 for hub_name , venvs in lock_specs .items ():
353439 for venv_name , lock in venvs .items ():
@@ -366,7 +452,8 @@ def _whl_install_repos(module_ctx, lock_specs):
366452 # only with the single venv. Shouldn't be possible to force this
367453 # target to build when the venv hub is not pointed to this venv.
368454 name = _whl_install_repo_name (hub_name , venv_name , package )
369- print ("Creating install repo" , name )
455+
456+ # print("Creating install repo", name)
370457 whl_install (
371458 name = name ,
372459 prebuilds = json .encode (prebuilds ),
@@ -385,7 +472,7 @@ def _marker_sha(marker):
385472 else :
386473 return None
387474
388- def _group_repos (module_ctx , lock_specs ):
475+ def _group_repos (module_ctx , lock_specs , entrypoint_specs ):
389476 # Hub -> requirement -> venv -> True
390477 # For building hubs we need to know what venv configurations a given
391478
@@ -534,7 +621,8 @@ def _group_repos(module_ctx, lock_specs):
534621 # their direct dependencies beyond the scc. So we can just lay down
535622 # targets.
536623 name = _venv_hub_name (hub_name , venv_name )
537- print ("Creating venv hub" , name )
624+
625+ # print("Creating venv hub", name)
538626 venv_hub (
539627 name = name ,
540628 aliases = scc_aliases , # String dict
@@ -546,13 +634,14 @@ def _group_repos(module_ctx, lock_specs):
546634 package : _whl_install_repo_name (hub_name , venv_name , {"name" : package })
547635 for package in sorted (graph .keys ())
548636 },
637+ entrypoints = json .encode (entrypoint_specs ),
549638 )
550639
551640 return package_venvs
552641
553- def _hub_repos (module_ctx , lock_specs , package_venvs ):
642+ def _hub_repos (module_ctx , lock_specs , package_venvs , entrypoint_specs ):
554643 for hub_name , packages in package_venvs .items ():
555- print ("Creating uv hub" , hub_name )
644+ # print("Creating uv hub", hub_name)
556645 hub_repo (
557646 name = hub_name ,
558647 hub_name = hub_name ,
@@ -561,6 +650,7 @@ def _hub_repos(module_ctx, lock_specs, package_venvs):
561650 package : venvs .keys ()
562651 for package , venvs in packages .items ()
563652 },
653+ entrypoints = json .encode (entrypoint_specs ),
564654 )
565655
566656def _uv_impl (module_ctx ):
@@ -578,6 +668,10 @@ def _uv_impl(module_ctx):
578668 # of conditions.
579669 configurations = _collect_configurations (module_ctx , lock_specs )
580670
671+ # Collect declared entrypoints for packages
672+ entrypoints = _collect_entrypoints (module_ctx , lock_specs )
673+ print ("got entrypoints" , entrypoints )
674+
581675 # Roll through and create sdist and whl repos for all configured sources
582676 # Note that these have no deps to this point
583677 _raw_sdist_repos (module_ctx , lock_specs )
@@ -587,13 +681,18 @@ def _uv_impl(module_ctx):
587681 _sbuild_repos (module_ctx , lock_specs )
588682
589683 # Roll through and create per-venv whl installs
684+ #
685+ # Note that we handle entrypoints at the venv level NOT the install level.
686+ # This is because we handle cycle breaking and deps at the venv level, so we
687+ # can't just take a direct dependency on the installed whl in its
688+ # implementation repo.
590689 _whl_install_repos (module_ctx , lock_specs )
591690
592691 # Roll through and create per-venv group/dep layers
593- package_venvs = _group_repos (module_ctx , lock_specs )
692+ package_venvs = _group_repos (module_ctx , lock_specs , entrypoints )
594693
595694 # Finally the hubs themselves are fully trivialized
596- _hub_repos (module_ctx , lock_specs , package_venvs )
695+ _hub_repos (module_ctx , lock_specs , package_venvs , entrypoints )
597696
598697 configurations_hub (
599698 name = "aspect_rules_py_pip_configurations" ,
@@ -622,11 +721,27 @@ _lockfile_tag = tag_class(
622721 },
623722)
624723
724+ _declare_entrypoint = tag_class (
725+ attrs = {
726+ "requirement" : attr .string (mandatory = True ),
727+ "name" : attr .string (mandatory = True ),
728+ "entrypoint" : attr .string (mandatory = True ),
729+ },
730+ )
731+
732+ _create_entrypoints = tag_class (
733+ attrs = {
734+ "requirement" : attr .string (mandatory = True ),
735+ },
736+ )
737+
625738uv = module_extension (
626739 implementation = _uv_impl ,
627740 tag_classes = {
628741 "declare_hub" : _hub_tag ,
629742 "declare_venv" : _venv_tag ,
630743 "lockfile" : _lockfile_tag ,
744+ "declare_entrypoint" : _declare_entrypoint ,
745+ "discover_entrypoints" : _create_entrypoints ,
631746 },
632747)
0 commit comments