From abc767edd5311886af19481bc65bc067ddc7f1a6 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 10 Oct 2022 17:05:51 +0200 Subject: [PATCH 01/14] Build: skip build based on commands' exit codes Define a particular exit code (439) to skip a build. If any of the commands returns this exit code, the build will be cancelled automatically and won't run any of the following commands. When this happens, the build will be marked as `cancelled` and no email/webhook notifications will be sent. Why 439 was chosen for the exit code? It's the word "skip" encoded in ASCII. Then it's taken the 256 modulo of it because the Unix implementation does this automatically for exit codes greater than 255 ``` >>> sum(list('skip'.encode('ascii'))) 439 >>> 439 % 256 183 ``` --- readthedocs/doc_builder/constants.py | 12 +++++++++--- readthedocs/doc_builder/environments.py | 5 ++++- readthedocs/doc_builder/exceptions.py | 7 +++++++ readthedocs/projects/tasks/builds.py | 3 +++ 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/readthedocs/doc_builder/constants.py b/readthedocs/doc_builder/constants.py index 50898dbc1ec..b034479b480 100644 --- a/readthedocs/doc_builder/constants.py +++ b/readthedocs/doc_builder/constants.py @@ -1,13 +1,11 @@ -# -*- coding: utf-8 -*- """Doc build constants.""" -import structlog import re +import structlog from django.conf import settings - log = structlog.get_logger(__name__) PDF_RE = re.compile('Output written on (.*?)') @@ -30,3 +28,11 @@ DOCKER_OOM_EXIT_CODE = 137 DOCKER_HOSTNAME_MAX_LEN = 64 + +# Why 183 exit code? +# +# >>> sum(list('skip'.encode('ascii'))) +# 439 +# >>> 439 % 256 +# 183 +RTD_SKIP_BUILD_EXIT_CODE = 183 diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py index 80a9e8e9f4d..979fc18dda8 100644 --- a/readthedocs/doc_builder/environments.py +++ b/readthedocs/doc_builder/environments.py @@ -30,8 +30,9 @@ DOCKER_SOCKET, DOCKER_TIMEOUT_EXIT_CODE, DOCKER_VERSION, + RTD_SKIP_BUILD_EXIT_CODE, ) -from .exceptions import BuildAppError, BuildUserError +from .exceptions import BuildAppError, BuildUserError, BuildUserSkip log = structlog.get_logger(__name__) @@ -468,6 +469,8 @@ def run_command_class( project_slug=self.project.slug if self.project else '', version_slug=self.version.slug if self.version else '', ) + elif build_cmd.exit_code == RTD_SKIP_BUILD_EXIT_CODE: + raise BuildUserSkip() else: # TODO: for now, this still outputs a generic error message # that is the same across all commands. We could improve this diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 8a7efab50a9..78465f16721 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -42,6 +42,13 @@ class BuildUserError(BuildBaseException): ) +class BuildUserSkip(BuildUserError): + message = gettext_noop( + "This build was cancelled due the magic exit code was returned by a commmand." + ) + state = BUILD_STATE_CANCELLED + + class ProjectBuildsSkippedError(BuildUserError): message = gettext_noop('Builds for this project are temporarily disabled') diff --git a/readthedocs/projects/tasks/builds.py b/readthedocs/projects/tasks/builds.py index 6553599bbbe..e33f3738399 100644 --- a/readthedocs/projects/tasks/builds.py +++ b/readthedocs/projects/tasks/builds.py @@ -44,6 +44,7 @@ BuildCancelled, BuildMaxConcurrencyError, BuildUserError, + BuildUserSkip, MkDocsYAMLParseError, ProjectBuildsSkippedError, YAMLParseError, @@ -280,6 +281,7 @@ class UpdateDocsTask(SyncRepositoryMixin, Task): YAMLParseError, BuildCancelled, BuildUserError, + BuildUserSkip, RepositoryError, MkDocsYAMLParseError, ProjectConfigurationError, @@ -289,6 +291,7 @@ class UpdateDocsTask(SyncRepositoryMixin, Task): exceptions_without_notifications = ( BuildCancelled, BuildMaxConcurrencyError, + BuildUserSkip, ProjectBuildsSkippedError, ) From cd91bf52444f5d04c85599107f77571adeee64e3 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Wed, 19 Oct 2022 11:10:43 +0200 Subject: [PATCH 02/14] Update readthedocs/doc_builder/exceptions.py Co-authored-by: Anthony --- readthedocs/doc_builder/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 78465f16721..3695377f6f0 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -44,7 +44,7 @@ class BuildUserError(BuildBaseException): class BuildUserSkip(BuildUserError): message = gettext_noop( - "This build was cancelled due the magic exit code was returned by a commmand." + "This build was manually skipped using a command exit code." ) state = BUILD_STATE_CANCELLED From 7c110dbb87748f6562f3bd40d92bf1d08a449eef Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 13:05:50 +0100 Subject: [PATCH 03/14] Build: send SUCCESS external build status on skipped builds When the command exists with the magic exit code, we send SUCCESS to GitHub/GitLab as status so the PR check passes and the PR can be merged. --- readthedocs/projects/tasks/builds.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/readthedocs/projects/tasks/builds.py b/readthedocs/projects/tasks/builds.py index e33f3738399..0070b7f7575 100644 --- a/readthedocs/projects/tasks/builds.py +++ b/readthedocs/projects/tasks/builds.py @@ -453,8 +453,8 @@ def on_failure(self, exc, task_id, args, kwargs, einfo): ) # Known errors in the user's project (e.g. invalid config file, invalid # repository, command failed, etc). Report the error back to the user - # using the `message` attribute from the exception itself. Otherwise, - # use a generic message. + # using the `message` and `state` attributes from the exception itself. + # Otherwise, use a generic message and default state. elif isinstance(exc, BuildUserError): if hasattr(exc, 'message') and exc.message is not None: self.data.build['error'] = exc.message @@ -493,6 +493,16 @@ def on_failure(self, exc, task_id, args, kwargs, einfo): version_type = None if self.data.version: version_type = self.data.version.type + + # NOTE: autoflake gets confused here. We need the NOQA for now. + status = BUILD_STATUS_FAILURE # noqa + if isinstance(exc, BuildUserSkip): + # The build was skipped by returning the magic exit code, + # marked as CANCELLED, but communicated to GitHub as successful. + # This is because the PR has to be available for merging when the build + # was skipped on purpose. + status = BUILD_STATUS_SUCCESS # noqa + send_external_build_status( version_type=version_type, build_pk=self.data.build['id'], From f67209c9df5ec2e1fd4d05554461f69f2fba44cc Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 13:23:28 +0100 Subject: [PATCH 04/14] Docs: skip build based on a condition --- docs/user/build-customization.rst | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index c8e833000fb..1d6a27fc5a6 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -73,6 +73,7 @@ There are some caveats to knowing when using user-defined jobs: * Environment variables are expanded in the commands (see :doc:`environment-variables`) * Each command is executed in a new shell process, so modifications done to the shell environment do not persist between commands * Any command returning non-zero exit code will cause the build to fail immediately + (note there is a special exit code to :ref:`skip the build `) * ``build.os`` and ``build.tools`` are required when using ``build.jobs`` @@ -104,6 +105,39 @@ To avoid this, it's possible to unshallow the clone done by Read the Docs: - git fetch --unshallow +Skip build based on a condition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There may be situations where you want to skip a build that was automatically triggered when someone on your team pushed to the repository. +Skipping builds will allow you to speed up review times and also help us to reduce costs in our servers. +The following situations are good examples to skip a build: + +* the build depends on an external situation that's not met yet +* there were no changes on the documentation files + +In these scenarios, you can skip the build by executing a custom command that checks for that particular condition +and exits with code ``439`` to skip it, or ``0`` to continue building the documentation normally. +If any of the commands return this particular exit code, +Read the Docs will stop the build immediately, +mark it as "Cancelled", +and communicate GitHub/GitLab that the build succeeded (green tick) so the pull request is in a mergeable state. + +.. code-block:: yaml + :caption: .readthedocs.yaml + + version: 2 + build: + os: "ubuntu-22.04" + tools: + python: "3.11" + jobs: + post_checkout: + # Check if there were changes in the "docs/" directory + # and return 439 to skip the build if there weren't + - ! git diff --quiet origin/main -- docs/ && exit 439 + + + Generate documentation from annotated sources with Doxygen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From ced96557511d024612eef37a0a4d2ee5c0a5c974 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 13:25:16 +0100 Subject: [PATCH 05/14] Lint: small darker change --- readthedocs/doc_builder/exceptions.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/readthedocs/doc_builder/exceptions.py b/readthedocs/doc_builder/exceptions.py index 3695377f6f0..6700393ca93 100644 --- a/readthedocs/doc_builder/exceptions.py +++ b/readthedocs/doc_builder/exceptions.py @@ -43,9 +43,7 @@ class BuildUserError(BuildBaseException): class BuildUserSkip(BuildUserError): - message = gettext_noop( - "This build was manually skipped using a command exit code." - ) + message = gettext_noop("This build was manually skipped using a command exit code.") state = BUILD_STATE_CANCELLED From b0c7e6fb548321e1afd814efa2075dbd50a0fa2d Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 13:43:24 +0100 Subject: [PATCH 06/14] Docs: reference fixed --- docs/user/build-customization.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index 1d6a27fc5a6..87e1ada5bd5 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -73,7 +73,7 @@ There are some caveats to knowing when using user-defined jobs: * Environment variables are expanded in the commands (see :doc:`environment-variables`) * Each command is executed in a new shell process, so modifications done to the shell environment do not persist between commands * Any command returning non-zero exit code will cause the build to fail immediately - (note there is a special exit code to :ref:`skip the build `) + (note there is a special exit code to `skip the build `_) * ``build.os`` and ``build.tools`` are required when using ``build.jobs`` From fc2dfa8b53e2f4569a79a4e97a4061046b1ea426 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 13:45:08 +0100 Subject: [PATCH 07/14] Docs: improve comment on YAML --- docs/user/build-customization.rst | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index 87e1ada5bd5..4e7575a48be 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -132,8 +132,12 @@ and communicate GitHub/GitLab that the build succeeded (green tick) so the pull python: "3.11" jobs: post_checkout: - # Check if there were changes in the "docs/" directory - # and return 439 to skip the build if there weren't + # Skip build when there aren't changed in docs directory. + # `--quiet` exits with a 1 when there **are** changes, + # so we invert the logic with a ! + # + # If there are no changes (exit 0) we force the command to return with 439. + # This is a special exit code on Read the Docs that will cancel the build immediately. - ! git diff --quiet origin/main -- docs/ && exit 439 From f9eecb4df63ba57b6cd5ad55e1fde15ab64883d2 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 16:23:05 +0100 Subject: [PATCH 08/14] Build: make usage of `status` variable Thanks to Benjamin for pointing it to me ;) --- readthedocs/projects/tasks/builds.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readthedocs/projects/tasks/builds.py b/readthedocs/projects/tasks/builds.py index 0070b7f7575..f5a72d2a0c3 100644 --- a/readthedocs/projects/tasks/builds.py +++ b/readthedocs/projects/tasks/builds.py @@ -495,19 +495,19 @@ def on_failure(self, exc, task_id, args, kwargs, einfo): version_type = self.data.version.type # NOTE: autoflake gets confused here. We need the NOQA for now. - status = BUILD_STATUS_FAILURE # noqa + status = BUILD_STATUS_FAILURE if isinstance(exc, BuildUserSkip): # The build was skipped by returning the magic exit code, # marked as CANCELLED, but communicated to GitHub as successful. # This is because the PR has to be available for merging when the build # was skipped on purpose. - status = BUILD_STATUS_SUCCESS # noqa + status = BUILD_STATUS_SUCCESS send_external_build_status( version_type=version_type, build_pk=self.data.build['id'], commit=self.data.build_commit, - status=BUILD_STATUS_FAILURE, + status=status, ) # Update build object From 0ca2fc6dec0bbd2f1da24eef2983f0525abc5715 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 16:32:05 +0100 Subject: [PATCH 09/14] Apply suggestions from code review Co-authored-by: Benjamin Balder Bach --- docs/user/build-customization.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index 4e7575a48be..1dc6ec05078 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -109,8 +109,8 @@ Skip build based on a condition ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There may be situations where you want to skip a build that was automatically triggered when someone on your team pushed to the repository. -Skipping builds will allow you to speed up review times and also help us to reduce costs in our servers. -The following situations are good examples to skip a build: +Skipping builds will allow you to speed up review times and also help us reduce server costs and ultimately our environmental footprint. +Consider the following scenarios: * the build depends on an external situation that's not met yet * there were no changes on the documentation files @@ -120,7 +120,9 @@ and exits with code ``439`` to skip it, or ``0`` to continue building the docume If any of the commands return this particular exit code, Read the Docs will stop the build immediately, mark it as "Cancelled", -and communicate GitHub/GitLab that the build succeeded (green tick) so the pull request is in a mergeable state. +and communicate to your Git platform (GitHub/GitLab) that the build succeeded (green tick ✅) so the pull request is in a mergeable state. + +Here is an example that exits the build when there are no changes to the ``docs/`` folder compared to the ``origin/main`` branch. .. code-block:: yaml :caption: .readthedocs.yaml From 428f45de44d3cd4d5af8d29bd53a16970dbfd019 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 16:44:19 +0100 Subject: [PATCH 10/14] Docs: update code example for skip builds Use Bash's `if` to only run this code on pull requests. --- docs/user/build-customization.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index 1dc6ec05078..cdeb30a6553 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -122,7 +122,7 @@ Read the Docs will stop the build immediately, mark it as "Cancelled", and communicate to your Git platform (GitHub/GitLab) that the build succeeded (green tick ✅) so the pull request is in a mergeable state. -Here is an example that exits the build when there are no changes to the ``docs/`` folder compared to the ``origin/main`` branch. +Here is an example that skip build from pull requests when there are no changes to the ``docs/`` folder compared to the ``origin/main`` branch. .. code-block:: yaml :caption: .readthedocs.yaml @@ -134,13 +134,13 @@ Here is an example that exits the build when there are no changes to the ``docs/ python: "3.11" jobs: post_checkout: - # Skip build when there aren't changed in docs directory. + # Skip building pull requests when there aren't changed in the docs directory. # `--quiet` exits with a 1 when there **are** changes, # so we invert the logic with a ! # # If there are no changes (exit 0) we force the command to return with 439. # This is a special exit code on Read the Docs that will cancel the build immediately. - - ! git diff --quiet origin/main -- docs/ && exit 439 + - if [ $READTHEDOCS_VERSION_TYPE = "external" ]; then ! git diff --quiet origin/main -- docs/ && exit 439; fi From 6ba8aae390eb3e1031af5cc92e736188201a3206 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Tue, 8 Nov 2022 17:45:37 +0100 Subject: [PATCH 11/14] Docs: example showing how to skip the build based on commit message --- docs/user/build-customization.rst | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index cdeb30a6553..aee84e1ebc2 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -122,7 +122,7 @@ Read the Docs will stop the build immediately, mark it as "Cancelled", and communicate to your Git platform (GitHub/GitLab) that the build succeeded (green tick ✅) so the pull request is in a mergeable state. -Here is an example that skip build from pull requests when there are no changes to the ``docs/`` folder compared to the ``origin/main`` branch. +Here is an example that skip build from pull requests when there are no changes to the ``docs/`` folder compared to the ``origin/main`` branch: .. code-block:: yaml :caption: .readthedocs.yaml @@ -143,6 +143,22 @@ Here is an example that skip build from pull requests when there are no changes - if [ $READTHEDOCS_VERSION_TYPE = "external" ]; then ! git diff --quiet origin/main -- docs/ && exit 439; fi +This other example shows how to skip a build if the commit message contains ``skip ci`` on it: + +.. code-block:: yaml + :caption: .readthedocs.yaml + + version: 2 + build: + os: "ubuntu-22.04" + tools: + python: "3.11" + jobs: + post_checkout: + # Use `git log` to check if the latest commit contains "skip ci", + # in that case exit the command with 439 to skip the build + - case `git --no-pager log --pretty="tformat:%s" -1` in *"skip ci"*) exit 439;; *);; esac + Generate documentation from annotated sources with Doxygen ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ From 111ee350847d5ce3857a24078a7b21692bd950fd Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 10 Nov 2022 09:21:44 +0100 Subject: [PATCH 12/14] Docs: refactor "skipping a build" section (#9717) * Docs: refactor "skipping a build" section - Move the explanation about the "Cancelling builds" feature to the "Builds" page - Keep the examples (How-To) for "Cancel a build" into the "Build customization" page * Docs: use 183 instead of 439 exit code > This error code isn't a valid exit > code (tldp.org/LDP/abs/html/exitcodes.html), we shouldn't document a code above > 255 otherwise users will get confused. From Eric's review. * Apply suggestions from code review Co-authored-by: Eric Holscher <25510+ericholscher@users.noreply.github.com> * Minor fix underline * Use multi-line bash examples They are easier to read * Update docs/user/builds.rst Co-authored-by: Eric Holscher <25510+ericholscher@users.noreply.github.com> Co-authored-by: Eric Holscher <25510+ericholscher@users.noreply.github.com> --- docs/user/build-customization.rst | 45 +++++++++++++++---------------- docs/user/builds.rst | 40 +++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index aee84e1ebc2..49681a0959a 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -73,7 +73,7 @@ There are some caveats to knowing when using user-defined jobs: * Environment variables are expanded in the commands (see :doc:`environment-variables`) * Each command is executed in a new shell process, so modifications done to the shell environment do not persist between commands * Any command returning non-zero exit code will cause the build to fail immediately - (note there is a special exit code to `skip the build `_) + (note there is a special exit code to `cancel the build `_) * ``build.os`` and ``build.tools`` are required when using ``build.jobs`` @@ -105,24 +105,14 @@ To avoid this, it's possible to unshallow the clone done by Read the Docs: - git fetch --unshallow -Skip build based on a condition -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Cancel build based on a condition +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -There may be situations where you want to skip a build that was automatically triggered when someone on your team pushed to the repository. -Skipping builds will allow you to speed up review times and also help us reduce server costs and ultimately our environmental footprint. -Consider the following scenarios: +When a command exits with code ``183``, +Read the Docs will cancel the build immediately. +You can use this approach to cancel builds that you don't want to complete based on some conditional logic. -* the build depends on an external situation that's not met yet -* there were no changes on the documentation files - -In these scenarios, you can skip the build by executing a custom command that checks for that particular condition -and exits with code ``439`` to skip it, or ``0`` to continue building the documentation normally. -If any of the commands return this particular exit code, -Read the Docs will stop the build immediately, -mark it as "Cancelled", -and communicate to your Git platform (GitHub/GitLab) that the build succeeded (green tick ✅) so the pull request is in a mergeable state. - -Here is an example that skip build from pull requests when there are no changes to the ``docs/`` folder compared to the ``origin/main`` branch: +Here is an example that cancels builds from pull requests when there are no changes to the ``docs/`` folder compared to the ``origin/main`` branch: .. code-block:: yaml :caption: .readthedocs.yaml @@ -134,16 +124,20 @@ Here is an example that skip build from pull requests when there are no changes python: "3.11" jobs: post_checkout: - # Skip building pull requests when there aren't changed in the docs directory. + # Cancel building pull requests when there aren't changed in the docs directory. # `--quiet` exits with a 1 when there **are** changes, # so we invert the logic with a ! # - # If there are no changes (exit 0) we force the command to return with 439. + # If there are no changes (exit 0) we force the command to return with 183. # This is a special exit code on Read the Docs that will cancel the build immediately. - - if [ $READTHEDOCS_VERSION_TYPE = "external" ]; then ! git diff --quiet origin/main -- docs/ && exit 439; fi + - | + if [ $READTHEDOCS_VERSION_TYPE = "external" ]; + then + ! git diff --quiet origin/main -- docs/ && exit 183; + fi -This other example shows how to skip a build if the commit message contains ``skip ci`` on it: +This other example shows how to cancel a build if the commit message contains ``skip ci`` on it: .. code-block:: yaml :caption: .readthedocs.yaml @@ -156,8 +150,13 @@ This other example shows how to skip a build if the commit message contains ``sk jobs: post_checkout: # Use `git log` to check if the latest commit contains "skip ci", - # in that case exit the command with 439 to skip the build - - case `git --no-pager log --pretty="tformat:%s" -1` in *"skip ci"*) exit 439;; *);; esac + # in that case exit the command with 183 to cancel the build + - | + case `git --no-pager log --pretty="tformat:%s" -1` + in *"skip ci"*) + exit 183;; + *);; + esac Generate documentation from annotated sources with Doxygen diff --git a/docs/user/builds.rst b/docs/user/builds.rst index 688bcd846e3..fe77b9aa72d 100644 --- a/docs/user/builds.rst +++ b/docs/user/builds.rst @@ -68,6 +68,46 @@ The following are the pre-defined jobs executed by Read the Docs: it's possible to run user-defined commands and :doc:`customize the build process `. +When to cancel builds +--------------------- + +There may be situations where you want to cancel a particular running build. +Cancelling running builds will allow your team to speed up review times and also help us reduce server costs and ultimately, +our environmental footprint. + +Consider the following scenarios: + +* the build has an external dependency that hasn't been updated +* there were no changes on the documentation files +* many other use cases that can be solved with custom logic + +For these scenarios, +Read the Docs supports three different mechanisms to cancel a running build: + +:Manually: + + Once a build was triggered, + project administrators can go to the build detail page + and click the button "Cancel build". + +:Automatically: + + When Read the Docs detects a push to a branch that it's currently building the documentation, + it cancels the running build and start a new build using the latest commit from the new push. + +:Programatically: + + You can use user-defined commands on ``build.jobs`` or ``build.commands`` (see :doc:`build-customization`) + to check for a condition and exit it with the code ``183`` if you want to cancel the running build or ``0``, otherwise. + + In this case, Read the Docs will communicate to your Git platform (GitHub/GitLab) that the build succeeded (green tick ✅) + so the pull request is in a mergeable state. + + .. tip:: + + Take a look at :ref:`build-customization:cancel build based on a condition` section for some examples. + + Build resources --------------- From a41cbf29ab2dadaa37fa95ee5e12f5c2d7e308ed Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Nov 2022 10:02:58 +0100 Subject: [PATCH 13/14] Docs: use `grep` instead of `case` --- docs/user/build-customization.rst | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index 49681a0959a..3c76836b8f3 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -151,12 +151,7 @@ This other example shows how to cancel a build if the commit message contains `` post_checkout: # Use `git log` to check if the latest commit contains "skip ci", # in that case exit the command with 183 to cancel the build - - | - case `git --no-pager log --pretty="tformat:%s" -1` - in *"skip ci"*) - exit 183;; - *);; - esac + - (git --no-pager log --pretty="tformat:%s -- %b" -1 | grep -viq "skip ci") || exit 183 Generate documentation from annotated sources with Doxygen From dc797016c0a20d38fbe95e95fb1baae61e1d1015 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 14 Nov 2022 10:11:03 +0100 Subject: [PATCH 14/14] Docs: small note about "Why 183?" --- docs/user/build-customization.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/user/build-customization.rst b/docs/user/build-customization.rst index 3c76836b8f3..18d56fa8a72 100644 --- a/docs/user/build-customization.rst +++ b/docs/user/build-customization.rst @@ -112,6 +112,21 @@ When a command exits with code ``183``, Read the Docs will cancel the build immediately. You can use this approach to cancel builds that you don't want to complete based on some conditional logic. +.. note:: Why 183 was chosen for the exit code? + + It's the word "skip" encoded in ASCII. + Then it's taken the 256 modulo of it because + `the Unix implementation does this automatically `_ + for exit codes greater than 255. + + .. code-block:: python + + >>> sum(list('skip'.encode('ascii'))) + 439 + >>> 439 % 256 + 183 + + Here is an example that cancels builds from pull requests when there are no changes to the ``docs/`` folder compared to the ``origin/main`` branch: .. code-block:: yaml