diff --git a/docs/source/contributing/releasing.rst b/docs/source/contributing/releasing.rst index 6f3cc167fc..e3842a5fce 100644 --- a/docs/source/contributing/releasing.rst +++ b/docs/source/contributing/releasing.rst @@ -165,7 +165,8 @@ the default branch, and ``tag`` pulled ``main`` before tagging. ``::warning`` naming both commits and what is missing, and the run stays green. A ``v*`` push of such a version still fails with the check's ``::error``. A partial release is finished by re-running the run that - tagged it. + tagged it, or, for Anaconda files that run's own ``conda`` legs cannot + build, by Conda Update's ``publish`` dispatch (see `Recovery`_). * ``pypi`` and ``conda`` attach their files with ``gh release upload``, in their "Upload package to GitHub Release" steps, not ``action-gh-release``, which calls Update a release before every upload (3.0.2's ``src/github.ts:631-683``). A file @@ -557,6 +558,14 @@ platform/Python distribution before uploading and skips only that leg's upload if it is already there. Neither replaces a file the GitHub Release already has (`#1640 `__). +A re-run repeats the tagged commit's own workflow, so it cannot upload +Anaconda files that commit's ``conda`` legs cannot build: 1.5.0b14's +``osx-64`` legs were broken at its tag (:issue:`2140`). While ``main``'s version +is still the tagged one, dispatching Conda Update with ``publish`` on ``main`` +builds ``main`` and uploads every platform under a bumped build +(:issue:`2060`, :pr:`2150`), and the warning a later commit's run posts for an +unfinished release names that remedy wherever Anaconda files are missing. + **A re-run does not need to be verified by hand against the PyPI and Anaconda listings, because** ``release_status`` **does it.** That job runs on every ``Create Release`` attempt regardless of what else skipped, and reconciles diff --git a/tests/project/test_release_gates.py b/tests/project/test_release_gates.py index e555fb82cf..3e74d1a8d4 100644 --- a/tests/project/test_release_gates.py +++ b/tests/project/test_release_gates.py @@ -3570,7 +3570,41 @@ def test_a_version_tagged_elsewhere_names_both_commits_and_the_remedy(self) -> N f'::warning title=v1.5.0b12 is unfinished (#1634)::v1.5.0b12 names {TAGGED_COMMIT}, not this ' f"run's commit {RUN_COMMIT}, and still lacks some of its PyPI files, some of its Anaconda files. " f"Only that commit's run may finish it (#1634), so this run released nothing: re-run the Create " - f'Release run created for {TAGGED_COMMIT}, or release the next version.', + f'Release run created for {TAGGED_COMMIT}, or release the next version. Its Anaconda files are the ' + "exception: while main's version is still 1.5.0b12, dispatch Conda Update with publish on main, " + 'which uploads every platform under a bumped build (#2060); the re-run finishes them only if that ' + "run's own conda legs can still succeed.", + ]) + + def test_missing_anaconda_files_name_the_conda_update_dispatch(self) -> None: + """#2140: 1.5.0b14's shape -- only Anaconda files lacking, which the tag's own legs could not build.""" + overrides = dict(next(row[1] for row in self.VECTORS + if row[0] == 'tag_on_another_commit_release_unfinished')) + overrides.update({'PCAPKIT_VERSION': '1.5.0b14'}) + result = self._run(overrides) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.splitlines(), [ + f'::warning title=v1.5.0b14 is unfinished (#1634)::v1.5.0b14 names {TAGGED_COMMIT}, not this ' + f"run's commit {RUN_COMMIT}, and still lacks some of its Anaconda files. Only that commit's run may " + f'finish it (#1634), so this run released nothing: re-run the Create Release run created for ' + f'{TAGGED_COMMIT}, or release the next version. Its Anaconda files are the exception: while ' + "main's version is still 1.5.0b14, dispatch Conda Update with publish on main, which uploads " + "every platform under a bumped build (#2060); the re-run finishes them only if that run's own " + 'conda legs can still succeed.', + ]) + + def test_complete_anaconda_files_keep_the_re_run_remedy_alone(self) -> None: + """#2140: with Anaconda complete, the PyPI files' remedy is still only the re-run.""" + overrides = dict(next(row[1] for row in self.VECTORS + if row[0] == 'tag_on_another_commit_release_unfinished')) + overrides.update({'PCAPKIT_PYPI_COMPLETE': 'false', 'PCAPKIT_CONDA_COMPLETE': 'true'}) + result = self._run(overrides) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(result.stdout.splitlines(), [ + f'::warning title=v1.5.0 is unfinished (#1634)::v1.5.0 names {TAGGED_COMMIT}, not this ' + f"run's commit {RUN_COMMIT}, and still lacks some of its PyPI files. Only that commit's run may " + f'finish it (#1634), so this run released nothing: re-run the Create Release run created for ' + f'{TAGGED_COMMIT}, or release the next version.', ]) def test_the_release_apps_tag_push_is_quiet(self) -> None: diff --git a/tests/project/test_release_status_scripts_1967.py b/tests/project/test_release_status_scripts_1967.py index 0fe21e65e5..ae559cfa89 100644 --- a/tests/project/test_release_status_scripts_1967.py +++ b/tests/project/test_release_status_scripts_1967.py @@ -6,7 +6,9 @@ cases below, and the script on the same, and requires the same status, standard output and error, ``$GITHUB_OUTPUT``, ``$GITHUB_STEP_SUMMARY`` and ``gh`` calls. The former body is kept here verbatim, and the case table is - checked to reach every branch of it. + checked to reach every branch of it. The one sentence the shell never had, + the Anaconda remedy #2140 added, is dropped from the script's output first; + :mod:`tests.project.test_release_gates` pins it. * :class:`TestTheVendorUpdateStaysInline` pins ``vendor_update``'s "Read whether its vendor-update job ran", which #1967 moved to a script and then put back inline as too plain to need one, to its shell verbatim, with no checkout, and @@ -270,6 +272,11 @@ 'PCAPKIT_RELEASED_AT': OTHER} #: A value standing for a variable left unset. UNSET = None +#: The sentence #2140 appended to the unfinished warning where Anaconda files are +#: missing, which the former shell never printed. +ANACONDA_REMEDY = re.compile(r"(?m) Its Anaconda files are the exception: while main's version is still .*?, " + r"dispatch Conda Update with publish on main, .*? only if that run's own conda legs " + r'can still succeed\.$') #: ``release_status``'s cases: ``(name, overrides of STATUS_BASE)``. STATUS_CASES = ( @@ -524,7 +531,7 @@ def test_the_release_status(self) -> None: for name, overrides in STATUS_CASES + tuple(sampled_status_cases()): with self.subTest(case=name): old, new = self.run_both(FORMER_STATUS, 'release_status.py', self.status_env(overrides)) - self.assertEqual(new, old) + self.assertEqual((new[0], ANACONDA_REMEDY.sub('', str(new[1])), *new[2:]), old) def test_every_status_branch_is_reached(self) -> None: """The curated cases alone reach each of :data:`STATUS_BRANCHES`, each case exactly one.""" diff --git a/util/release_status.py b/util/release_status.py index a8423b7dfa..97d606a35c 100644 --- a/util/release_status.py +++ b/util/release_status.py @@ -18,7 +18,8 @@ 2. ``version_check`` did not succeed: a warning. 3. The release test gate failed or was cancelled: a warning. 4. The version is tagged at another commit and every publisher skipped (#1748): - a notice if that release is complete, else a warning naming what it lacks. + a notice if that release is complete, else a warning naming what it lacks, + and, for Anaconda files, Conda Update's ``publish`` dispatch (#2140). 5. Each publisher reconciled against its own evidence -- succeeded, or skipped with its evidence saying done: a notice if all reconciled, a warning if one failed or was cancelled, and otherwise the ``::error``. @@ -141,11 +142,18 @@ def report_elsewhere() -> 'None': '(#1748).') return version = var('PCAPKIT_VERSION') + # #2140: the re-run repeats the tagged commit's own conda legs, so it cannot + # upload what they could not build -- 1.5.0b14's osx-64, broken at its tag. + # Conda Update's `publish` dispatch builds main instead (#2060). + anaconda = (f" Its Anaconda files are the exception: while main's version is still {version}, dispatch Conda " + 'Update with publish on main, which uploads every platform under a bumped build (#2060); the ' + "re-run finishes them only if that run's own conda legs can still succeed." + if var('PCAPKIT_CONDA_COMPLETE') != 'true' else '') say('warning', f'v{version} is unfinished (#1634)', f"v{var('PCAPKIT_VERSION')} names {var('PCAPKIT_RELEASED_AT')}, not this run's commit " f"{var('PCAPKIT_RUN_SHA')}, and still lacks {', '.join(missing)}. Only that commit's run may finish it " '(#1634), so this run released nothing: re-run the Create Release run created for ' - f"{var('PCAPKIT_RELEASED_AT')}, or release the next version.") + f"{var('PCAPKIT_RELEASED_AT')}, or release the next version.{anaconda}") def jobs() -> 'str':