@@ -297,7 +297,6 @@ def check_tool(db: DbfilenameShelf, tool: str) -> None:
297297
298298
299299check_git = functools .partial (check_tool , tool = "git" )
300- check_latexmk = functools .partial (check_tool , tool = "latexmk" )
301300check_make = functools .partial (check_tool , tool = "make" )
302301check_blurb = functools .partial (check_tool , tool = "blurb" )
303302check_autoconf = functools .partial (check_tool , tool = "autoconf" )
@@ -474,39 +473,63 @@ def create_tag(db: DbfilenameShelf) -> None:
474473 )
475474
476475
477- def build_release_artifacts (db : DbfilenameShelf ) -> None :
478- with cd (db ["git_repo" ]):
479- release_mod .export (db ["release" ])
480-
481-
482- def test_release_artifacts (db : DbfilenameShelf ) -> None :
483- with tempfile .TemporaryDirectory () as tmppath :
484- the_dir = pathlib .Path (tmppath )
485- the_dir .mkdir (exist_ok = True )
486- filename = f"Python-{ db ['release' ]} "
487- tarball = f"Python-{ db ['release' ]} .tgz"
488- shutil .copy2 (
489- db ["git_repo" ] / str (db ["release" ]) / "src" / tarball ,
490- the_dir / tarball ,
491- )
492- subprocess .check_call (["tar" , "xvf" , tarball ], cwd = the_dir )
493- subprocess .check_call (
494- ["./configure" , "--prefix" , str (the_dir / "installation" )],
495- cwd = the_dir / filename ,
496- )
497- subprocess .check_call (["make" , "-j" ], cwd = the_dir / filename )
498- subprocess .check_call (["make" , "install" , "-j" ], cwd = the_dir / filename )
499- process = subprocess .run (
500- ["./bin/python3" , "-m" , "test" , "-uall" ],
501- cwd = str (the_dir / "installation" ),
502- text = True ,
503- )
476+ def wait_for_source_and_docs_artifacts (db : DbfilenameShelf ) -> None :
477+ # Determine if we need to wait for docs or only source artifacts.
478+ release_tag = db ["release" ]
479+ should_wait_for_docs = release_tag .is_final or release_tag .is_release_candiate
504480
505- if process .returncode == 0 :
506- return
481+ # Create the directory so it's easier to place the artifacts there.
482+ release_path = pathlib .Path (db ["git_repo" ] / str (release_tag ))
483+ release_path .mkdir (parents = True , exist_ok = True )
507484
508- if not ask_question ("Some test_failed! Do you want to continue?" ):
509- raise ReleaseException ("Test failed!" )
485+ # Build the list of filepaths we're expecting.
486+ wait_for_paths = [
487+ release_path / "src" / f"Python-{ release_tag } .tgz" ,
488+ release_path / "src" / f"Python-{ release_tag } .tar.xz"
489+ ]
490+ if should_wait_for_docs :
491+ docs_path = release_path / "docs"
492+ wait_for_paths .extend ([
493+ docs_path / f"python-{ release_tag } -docs.epub" ,
494+ docs_path / f"python-{ release_tag } -docs-html.tar.bz2" ,
495+ docs_path / f"python-{ release_tag } -docs-html.zip" ,
496+ docs_path / f"python-{ release_tag } -docs-pdf-a4.tar.bz2" ,
497+ docs_path / f"python-{ release_tag } -docs-pdf-a4.zip" ,
498+ docs_path / f"python-{ release_tag } -docs-pdf-letter.tar.bz2" ,
499+ docs_path / f"python-{ release_tag } -docs-pdf-letter.zip" ,
500+ docs_path / f"python-{ release_tag } -docs-texinfo.tar.bz2" ,
501+ docs_path / f"python-{ release_tag } -docs-texinfo.zip" ,
502+ docs_path / f"python-{ release_tag } -docs-text.tar.bz2" ,
503+ docs_path / f"python-{ release_tag } -docs-text.zip" ,
504+ ])
505+
506+ print (f"Waiting for source{ ' and docs' if should_wait_for_docs else '' } artifacts to be built" )
507+ print (f"Artifacts should be placed at '{ release_path } ':" )
508+ for path in wait_for_paths :
509+ print (f"- '{ os .path .relpath (path , release_path )} '" )
510+
511+ while not all (path .exists () for path in wait_for_paths ):
512+ time .sleep (1 )
513+
514+
515+ def sign_source_artifacts (db : DbfilenameShelf ) -> None :
516+ print ('Signing tarballs with GPG' )
517+ uid = os .environ .get ("GPG_KEY_FOR_RELEASE" )
518+ if not uid :
519+ print ('List of available private keys:' )
520+ subprocess .check_call ('gpg -K | grep -A 1 "^sec"' , shell = True )
521+ uid = input ('Please enter key ID to use for signing: ' )
522+
523+ tarballs_path = pathlib .Path (db ["git_repo" ] / str (db ["release" ]) / "src" )
524+ tgz = str (tarballs_path / ("Python-%s.tgz" % db ["release" ]))
525+ xz = str (tarballs_path / ("Python-%s.tar.xz" % db ["release" ]))
526+
527+ subprocess .check_call (['gpg' , '-bas' , '-u' , uid , tgz ])
528+ subprocess .check_call (['gpg' , '-bas' , '-u' , uid , xz ])
529+
530+ print ('Signing tarballs with Sigstore' )
531+ subprocess .check_call (['python3' , '-m' , 'sigstore' , 'sign' ,
532+ '--oidc-disable-ambient-providers' , tgz , xz ])
510533
511534
512535def build_sbom_artifacts (db ):
@@ -698,9 +721,55 @@ def execute_command(command):
698721 execute_command (f"find { destination } -type f -exec chmod 664 {{}} \\ ;" )
699722
700723
724+ def start_build_of_source_and_docs (db : DbfilenameShelf ) -> None :
725+ # Get the git commit SHA for the tag
726+ commit_sha = subprocess .check_output (
727+ ["git" , "rev-list" , "-n" , "1" , db ["release" ].gitname ],
728+ cwd = db ["git_repo" ]
729+ ).decode ().strip ()
730+
731+ # Get the owner of the GitHub repo (first path segment in a 'github.com' remote URL)
732+ # This works for both 'https' and 'ssh' style remote URLs.
733+ origin_remote_url = subprocess .check_output (
734+ ["git" , "ls-remote" , "--get-url" , "origin" ],
735+ cwd = db ["git_repo" ]
736+ ).decode ().strip ()
737+ match = re .match (r"github\.com/([^/]+)/" , origin_remote_url )
738+ if not match :
739+ raise ReleaseException (f"Could not parse GitHub owner from 'origin' remote URL: { origin_remote_url } " )
740+ origin_remote_github_owner = match .group (1 )
741+
742+ # We ask for human verification at this point since this commit SHA is 'locked in'
743+ print ()
744+ print (f"Go to https://github.com/{ origin_remote_github_owner } /cpython/commit/{ commit_sha } " )
745+ print ("- Ensure that there is no warning that the commit does not belong to this repository." )
746+ print ("- Ensure that the commit diff does not contain any unexpected changes." )
747+ print ("- For the next step, ensure the commit SHA matches the one you verified on GitHub in this step." )
748+ print ()
749+ if not ask_question (
750+ "Have you verified the release commit hasn't been tampered with on GitHub?"
751+ ):
752+ raise ReleaseException ("Commit must be visually reviewed before starting build" )
753+
754+ # After visually confirming the release manager can start the build process
755+ # with the known good commit SHA.
756+ print ()
757+ print ("Go to https://github.com/python/release-tools/actions/workflows/source-and-docs-release.yml" )
758+ print ("Select 'Run workflow' and enter the following values:" )
759+ print (f"- Git remote to checkout: { origin_remote_github_owner } " )
760+ print (f"- Git commit to target for the release: { commit_sha } " )
761+ print (f"- CPython release number: { db ['release' ]} " )
762+ print ()
763+
764+ if not ask_question (
765+ "Have you started the source and docs build?"
766+ ):
767+ raise ReleaseException ("Source and docs build must be started" )
768+
769+
701770def send_email_to_platform_release_managers (db : DbfilenameShelf ) -> None :
702771 if not ask_question (
703- "Have you notified the platform release managers about the availability of artifacts ?"
772+ "Have you notified the platform release managers about the availability of the commit SHA and tag ?"
704773 ):
705774 raise ReleaseException ("Platform release managers muy be notified" )
706775
@@ -1040,9 +1109,9 @@ def _api_key(api_key):
10401109 )
10411110 args = parser .parse_args ()
10421111 auth_key = args .auth_key or os .getenv ("AUTH_INFO" )
1112+ assert isinstance (auth_key , str ), "We need an AUTH_INFO env var or --auth-key"
10431113 tasks = [
10441114 Task (check_git , "Checking git is available" ),
1045- Task (check_latexmk , "Checking latexmk is available" ),
10461115 Task (check_make , "Checking make is available" ),
10471116 Task (check_blurb , "Checking blurb is available" ),
10481117 Task (check_docker , "Checking docker is available" ),
@@ -1061,18 +1130,19 @@ def _api_key(api_key):
10611130 Task (bump_version , "Bump version" ),
10621131 Task (check_cpython_repo_is_clean , "Checking git repository is clean" ),
10631132 Task (create_tag , "Create tag" ),
1064- Task (build_release_artifacts , "Building release artifacts" ),
1065- Task (test_release_artifacts , "Test release artifacts" ),
1133+ Task (push_to_local_fork , "Push new tags and branches to private fork" ),
1134+ Task (start_build_of_source_and_docs , "Start the builds for source and docs artifacts" ),
1135+ Task (
1136+ send_email_to_platform_release_managers ,
1137+ "Platform release managers have been notified of the commit SHA" ,
1138+ ),
1139+ Task (wait_for_source_and_docs_artifacts , "Wait for source and docs artifacts to build" ),
10661140 Task (build_sbom_artifacts , "Building SBOM artifacts" ),
1141+ Task (sign_source_artifacts , "Sign source artifacts" ),
10671142 Task (upload_files_to_server , "Upload files to the PSF server" ),
10681143 Task (place_files_in_download_folder , "Place files in the download folder" ),
10691144 Task (upload_docs_to_the_docs_server , "Upload docs to the PSF docs server" ),
10701145 Task (unpack_docs_in_the_docs_server , "Place docs files in the docs folder" ),
1071- Task (push_to_local_fork , "Push new tags and branches to private fork" ),
1072- Task (
1073- send_email_to_platform_release_managers ,
1074- "Platform release managers have been notified of the release artifacts" ,
1075- ),
10761146 Task (wait_util_all_files_are_in_folder , "Wait until all files are ready" ),
10771147 Task (create_release_object_in_db , "The django release object has been created" ),
10781148 Task (post_release_merge , "Merge the tag into the release branch" ),
0 commit comments