From b613c6d59cfbbda69c9811e883d2042f64436089 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 00:52:13 +0800 Subject: [PATCH 01/25] snap: Switch Leptonica source to Git repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch mitigates risk of supply chain attack via tainted source release packages(e.g. CVE-2024-3094). Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index b67705558d..1515e5488f 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -41,7 +41,8 @@ parts: - libgomp1 after: [leptonica] leptonica: - source: https://github.com/DanBloomberg/leptonica/archive/1.83.1.tar.gz + source: https://github.com/DanBloomberg/leptonica.git + source-tag: 1.83.1 plugin: autotools stage-packages: - libjbig0 From 66afa2ce1df2822c6078a32f4bde44c6101a0945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 01:01:04 +0800 Subject: [PATCH 02/25] snap: Fix Leptonica part not build with OpenJPEG support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch addresses the following Snapcraft linter warning: ``` - library: libopenjp2.so.7: unused library 'usr/lib/x86_64-linux-gnu/libo penjp2.so.2.4.0'. (https://snapcraft.io/docs/linters-library) ``` Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 1515e5488f..30a91e754a 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -44,6 +44,8 @@ parts: source: https://github.com/DanBloomberg/leptonica.git source-tag: 1.83.1 plugin: autotools + build-packages: + - libopenjp2-7-dev stage-packages: - libjbig0 - libjpeg-turbo8 From 0649d0f81b3e6345675725f4224a7609917add45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 01:28:41 +0800 Subject: [PATCH 03/25] snap: Fix missing {build,stage}-packages declaration for the Leptonica part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 30a91e754a..616bdbc14a 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -45,9 +45,14 @@ parts: source-tag: 1.83.1 plugin: autotools build-packages: + - libjpeg-dev - libopenjp2-7-dev + - libpng-dev + - libtiff-dev + - pkg-config stage-packages: - libjbig0 - libjpeg-turbo8 - libopenjp2-7 + - libpng16-16 - libtiff5 From 7e9dd2d95ee19251f4ede651f8872db405f6f613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 01:29:44 +0800 Subject: [PATCH 04/25] snap: Ensure Leptonica features we need are built MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 616bdbc14a..70cd41fd0a 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -56,3 +56,9 @@ parts: - libopenjp2-7 - libpng16-16 - libtiff5 + autotools-configure-parameters: + # Ensure features are built + - --with-libpng + - --with-jpeg + - --with-libtiff + - --with-libopenjpeg From 064a6b0f28e1161936838f0119c7b041c9a2c02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 01:32:56 +0800 Subject: [PATCH 05/25] snap: Optimize Leptonica part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reduce part pull time by limiting the history depth to clone * Reduce part build time by disable components that we don't need. * Reduce overall snap size by only shipping files that are needed in runtime. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 70cd41fd0a..01e96fe849 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -43,6 +43,7 @@ parts: leptonica: source: https://github.com/DanBloomberg/leptonica.git source-tag: 1.83.1 + source-depth: 1 plugin: autotools build-packages: - libjpeg-dev @@ -57,8 +58,17 @@ parts: - libpng16-16 - libtiff5 autotools-configure-parameters: + # Reduce part build time + - --disable-programs + - --disable-static + - --disable-dependency-tracking + # Ensure features are built - --with-libpng - --with-jpeg - --with-libtiff - --with-libopenjpeg + prime: + - usr/lib/$CRAFT_ARCH_TRIPLET/*.so* + - usr/local/lib/*.so* + - usr/share/doc/*/copyright* From 8d9174a86e70ff8a221e10de8e9d61f698b75853 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 18:39:57 +0800 Subject: [PATCH 06/25] snap: Update Leptonica to 1.84.1 version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the current latest version. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 01e96fe849..d7c4442875 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -42,7 +42,7 @@ parts: after: [leptonica] leptonica: source: https://github.com/DanBloomberg/leptonica.git - source-tag: 1.83.1 + source-tag: 1.84.1 source-depth: 1 plugin: autotools build-packages: From b28ab15798f38cb0c69c3fc5c97e5111045eacb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 16:39:36 +0800 Subject: [PATCH 07/25] snap: Fix missing WebP support in the Leptonica part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d7c4442875..3ca8e485d6 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -50,6 +50,7 @@ parts: - libopenjp2-7-dev - libpng-dev - libtiff-dev + - libwebp-dev - pkg-config stage-packages: - libjbig0 @@ -57,6 +58,8 @@ parts: - libopenjp2-7 - libpng16-16 - libtiff5 + - libwebp7 + - libwebpmux3 autotools-configure-parameters: # Reduce part build time - --disable-programs @@ -67,6 +70,7 @@ parts: - --with-libpng - --with-jpeg - --with-libtiff + - --with-libwebp - --with-libopenjpeg prime: - usr/lib/$CRAFT_ARCH_TRIPLET/*.so* From ef199efa3fd6ae4d34cc7d0b4ee916429873ff65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 17:41:44 +0800 Subject: [PATCH 08/25] snap: Fix missing stage-packages of the Tesseract part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The application is still runnable due to some of the packages are already staged by the Leptonica part, however it should still be declared for clarity. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 3ca8e485d6..a8fd4cf261 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -38,7 +38,12 @@ parts: - libpango1.0-dev - libcairo2-dev stage-packages: + - libcairo2 - libgomp1 + - libicu70 + - libpango-1.0-0 + - libtiff5 + - zlib1g after: [leptonica] leptonica: source: https://github.com/DanBloomberg/leptonica.git From 8782ee42007683a6fb17f1d77afde3474cc8cc5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 17:48:50 +0800 Subject: [PATCH 09/25] snap: Reduce package size by stripping out files not used in runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change reduces package size by 44%(73637888 -> 40894464). Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a8fd4cf261..c9e44c8224 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -45,6 +45,14 @@ parts: - libtiff5 - zlib1g after: [leptonica] + prime: + - -usr/bin/fc-* + - -usr/local/include + - -usr/local/lib/*.a + - -usr/local/lib/*.la + - -usr/local/lib/pkgconfig + - -usr/share/lintian + - -usr/share/man leptonica: source: https://github.com/DanBloomberg/leptonica.git source-tag: 1.84.1 From 19270b48d3ade5597d47184599797be52a1e88bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 18:42:22 +0800 Subject: [PATCH 10/25] snap: Drop unused training tools build/runtime dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently this feature isn't exposed to the host, dropping the dependencies to reduce built package size. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c9e44c8224..d62be03197 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -34,19 +34,12 @@ parts: - libjpeg-dev - libtiff-dev - zlib1g-dev - - libicu-dev - - libpango1.0-dev - - libcairo2-dev stage-packages: - - libcairo2 - libgomp1 - - libicu70 - - libpango-1.0-0 - libtiff5 - zlib1g after: [leptonica] prime: - - -usr/bin/fc-* - -usr/local/include - -usr/local/lib/*.a - -usr/local/lib/*.la From db818ac16686f05f34caa6ae79abf073c74c0d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 18:21:03 +0800 Subject: [PATCH 11/25] snap: Explicitly declare features we don't want to be build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously this is implicitly configured as the build dependency isn't available and the training tools build target isn't called. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d62be03197..9035275f60 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -39,6 +39,9 @@ parts: - libtiff5 - zlib1g after: [leptonica] + autotools-configure-parameters: + - --disable-doc + - --disable-training prime: - -usr/local/include - -usr/local/lib/*.a From 7a7a984d70a1bd280e45cb84eca10b858cacaa89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 18:21:17 +0800 Subject: [PATCH 12/25] snap: Disable unused features to reduce snap size and built time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9035275f60..3c07f74b0c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -40,12 +40,13 @@ parts: - zlib1g after: [leptonica] autotools-configure-parameters: + # Disable unused features to reduce snap size and built time + - --disable-dependency-tracking - --disable-doc + - --disable-static - --disable-training prime: - -usr/local/include - - -usr/local/lib/*.a - - -usr/local/lib/*.la - -usr/local/lib/pkgconfig - -usr/share/lintian - -usr/share/man From 7d2ba189ad11265f48ecdf95775181460f85430a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 18:28:27 +0800 Subject: [PATCH 13/25] snap: Fix incorrect dependency declaration for the Tesseract part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The zlib library is Leptonica's dependency, not Tesseract's. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 3c07f74b0c..d3e2e43dfe 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -33,11 +33,9 @@ parts: - libpng-dev - libjpeg-dev - libtiff-dev - - zlib1g-dev stage-packages: - libgomp1 - libtiff5 - - zlib1g after: [leptonica] autotools-configure-parameters: # Disable unused features to reduce snap size and built time @@ -62,6 +60,7 @@ parts: - libtiff-dev - libwebp-dev - pkg-config + - zlib1g-dev stage-packages: - libjbig0 - libjpeg-turbo8 @@ -70,6 +69,7 @@ parts: - libtiff5 - libwebp7 - libwebpmux3 + - zlib1g autotools-configure-parameters: # Reduce part build time - --disable-programs From 23b31c623b13725b564cb61b16fc3a581a16cf39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 18:48:29 +0800 Subject: [PATCH 14/25] snap: Drop incorrect build-packages declaration of the Tesseract part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These should be Leptonica part's build dependencies. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d3e2e43dfe..890cd0356a 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -30,8 +30,6 @@ parts: plugin: autotools build-packages: - pkg-config - - libpng-dev - - libjpeg-dev - libtiff-dev stage-packages: - libgomp1 From d05286d138aa340ad8b24205b29bc92075066e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 18:51:32 +0800 Subject: [PATCH 15/25] snap: Add cosmetic blank line separator between the definition of the tesseract and leptonica part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 890cd0356a..c845c9a75d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -46,6 +46,7 @@ parts: - -usr/local/lib/pkgconfig - -usr/share/lintian - -usr/share/man + leptonica: source: https://github.com/DanBloomberg/leptonica.git source-tag: 1.84.1 From 53f2b00de69d15af2c15ca3102f0e73fac91497c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 19:00:37 +0800 Subject: [PATCH 16/25] snap: Improve order of the `after` part property MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c845c9a75d..a0688cb7e2 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -26,6 +26,7 @@ apps: parts: tesseract: + after: [leptonica] source: . plugin: autotools build-packages: @@ -34,7 +35,6 @@ parts: stage-packages: - libgomp1 - libtiff5 - after: [leptonica] autotools-configure-parameters: # Disable unused features to reduce snap size and built time - --disable-dependency-tracking From 89a673cde045720809212fcdd57c61d6a151b4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 19:09:02 +0800 Subject: [PATCH 17/25] snap: Incorporate v3.0.6 selective-checkout scriptlet for ease of stable snap publishing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This scriptlet allows stable builds to be always available for the downstream snap publisher to promote to the `stable` channel on the Snap Store. Higher risk builds(e.g. beta/edge) will only occur when the lower risk builds(rc/stable) are promoted. Refer the following Snapcraft Forum topic for more info: Selective-checkout: Check out the tagged release revision if it isn't promoted to the stable channel - doc - snapcraft.io https://forum.snapcraft.io/t/selective-checkout-check-out-the-tagged-release-revision-if-it-isnt-promoted-to-the-stable-channel/10617 Here's a snippet of the scriptlet in action: ``` :: selective-checkout: INFO: Detecting stable releases... :: selective-checkout: determine_stable_release_details: DEBUG: Last stable release tag determines to be "5.4.1". :: selective-checkout: determine_stable_release_details: DEBUG: Last stable version determines to be "5.4.1". :: selective-checkout: snap_query_version: DEBUG: Checking what snap revisions are available for the tesseract snap at the stable release channel... :: selective-checkout: determine_stable_release_details: DEBUG: Last stable version on the snap store determines to be "". :: selective-checkout: INFO: Detecting release candidate releases... :: selective-checkout: DEBUG: Last release candidate release tag determines to be "5.4.0-rc2". :: selective-checkout: DEBUG: Last release candidate version determines to be "5.4.0-rc2". :: selective-checkout: snap_query_version: DEBUG: Checking what snap revisions are available for the tesseract snap at the candidate release channel... :: selective-checkout: DEBUG: Last release candidate version on the snap store determines to be "". :: selective-checkout: INFO: Detecting beta releases... :: selective-checkout: DEBUG: Last beta release tag determines to be "5.0.0-beta-20210916". :: selective-checkout: DEBUG: Last beta version determines to be "5.0.0-beta-20210916". :: selective-checkout: snap_query_version: DEBUG: Checking what snap revisions are available for the tesseract snap at the beta release channel... :: selective-checkout: DEBUG: Last beta version on the snap store determines to be "". :: selective-checkout: determine_which_version_to_build: DEBUG: Stable version(5.4.1) is newer than the one on the Snap Store(none), stable release will be built. :: selective-checkout: Info: The last tagged stable release(5.4.1) hasn't been promoted to the stable channel() on the Snap Store yet, checking out 5.4.1. ``` Signed-off-by: 林博仁(Buo-ren Lin) --- snap/local/selective-checkout | 1866 +++++++++++++++++++++++++++++++++ snap/snapcraft.yaml | 11 +- 2 files changed, 1876 insertions(+), 1 deletion(-) create mode 100755 snap/local/selective-checkout diff --git a/snap/local/selective-checkout b/snap/local/selective-checkout new file mode 100755 index 0000000000..fbcaa71b26 --- /dev/null +++ b/snap/local/selective-checkout @@ -0,0 +1,1866 @@ +#!/usr/bin/env bash +# This scriptlet enhances the pull step that will only build +# development snapshots snaps if the latest tagged release has been +# promoted to the stable channel. This ensures that there's always +# a revision of the stable release snap available in the edge channel +# for the publisher to promote to stable as currently the build +# infrastructure only supports build on code push (but not new tagged +# releases) at this time. +# https://forum.snapcraft.io/t/selective-checkout-check-out-the-tagged-release-revision-if-it-isnt-promoted-to-the-stable-channel/10617 +# +# Copyright 2024 林博仁(Buo-ren, Lin) +# SPDX-License-Identifier: CC-BY-SA-4.0 + +SELECTIVE_CHECKOUT_DEBUG="${SELECTIVE_CHECKOUT_DEBUG:-false}" + +set \ + -o errexit \ + -o errtrace \ + -o nounset \ + -o pipefail + +for required_command in \ + curl \ + cut \ + head \ + jq \ + realpath \ + sed \ + sort \ + tail \ + tr; do + if ! command -v "${required_command}" >/dev/null; then + printf -- \ + 'Fatal: This script requires the "%s" command in your command search PATHs.\n' \ + "${required_command}" \ + >&2 + exit 1 + fi +done + +init(){ + script="$( + realpath \ + --strip \ + "${BASH_SOURCE[0]}" + )" + script_filename="${script##*/}" + script_name="${script_filename%%.*}" + export SCRIPT_NAME="${script_name}" + + # checkout_mode: + # - snapshot: Build as-is + # - release: Build the latest tagged release + # tag_pattern_release: We assume all tags contains dots or underscores release tags + # + # Indirection used + # shellcheck disable=SC2034 + local \ + checkout_mode \ + flag_append_packaging_version=false \ + flag_dry_run=false \ + flag_debug_tracing=false \ + flag_force_snapshot=false \ + flag_force_stable=false \ + packaging_revision \ + postfix_dirty_marker_packaging=-d \ + postfix_dirty_marker_upstream=-dirty \ + tag_pattern_beta='-beta[[:digit:]]+$' \ + tag_pattern_release='.*[._].*' \ + tag_pattern_release_candidate='-rc[[:digit:]]+$' \ + tag_pattern_stable \ + tag_prefix_release=v \ + revision_minimal_length_packaging=4 \ + revision_minimal_length_upstream=7 \ + snap_version \ + snap_version_postfix_seperator=+ \ + upstream_version + + if ! determining_runtime_parameters \ + flag_append_packaging_version \ + tag_pattern_beta \ + flag_debug_tracing \ + flag_dry_run \ + flag_force_snapshot \ + flag_force_stable \ + postfix_dirty_marker_packaging \ + revision_minimal_length_packaging \ + tag_pattern_release_candidate \ + tag_pattern_release \ + tag_prefix_release \ + snap_version_postfix_seperator \ + tag_pattern_stable \ + postfix_dirty_marker_upstream \ + revision_minimal_length_upstream \ + "${@}"; then + printf \ + 'Error: Error(s) occurred while determining the runtime parameters.\n' \ + 1>&2 + exit 1 + fi + + if test "${flag_debug_tracing}" = true; then + set -o xtrace + fi + + vcs_check_runtime_dependencies \ + "${PWD}" + + if test "${flag_force_snapshot}" = true; then + printf -- \ + '%s: Info: Force building development snapshots\n' \ + "${script_name}" + checkout_mode=snapshot + elif test "$(vcs_detect "${PWD}")" = not_found; then + printf -- \ + '%s: Info: Build from source archive\n' \ + "${script_name}" + checkout_mode=snapshot + elif vcs_is_dirty \ + "${PWD}"; then + # If tracked files are modified + # or staging area not empty + printf -- \ + '%s: Info: Working tree is dirty, building development snapshot with additional changes\n' \ + "${script_name}" + checkout_mode=snapshot + elif ! \ + vcs_has_release_tags \ + "${PWD}" \ + "${tag_pattern_release}"; then + printf -- \ + '%s: Warning: No release tags found, assuming building from development snapshots.\n' \ + "${script_name}" \ + 1>&2 + checkout_mode=snapshot + else + printf -- \ + '%s: Info: Determining version to be built...\n' \ + "${script_name}" + local \ + release_type_to_build=development-snapshot \ + last_stable_tag \ + last_stable_version \ + last_stable_version_on_the_snap_store \ + last_release_candidate_tag \ + last_release_candidate_version \ + last_release_candidate_version_on_the_snap_store \ + last_beta_tag \ + last_beta_version \ + last_beta_version_on_the_snap_store + + local -a all_release_tags=() + local -A \ + map_of_tag_to_normalized_version \ + map_of_release_type_to_snap_channel + + map_of_release_type_to_snap_channel=( + [stable]=stable + [release-candidate]=candidate + [beta]=beta + [development-snapshot]=edge + ) + + if ! { + test -v CRAFT_PROJECT_NAME \ + || test -v SNAPCRAFT_PROJECT_NAME + }; then + printf -- \ + "%s: Error: This script requires either the CRAFT_PROJECT_NAME or the SNAPCRAFT_PROJECT_NAME environment variable to be set.\\n" \ + "${script_name}" \ + >&2 + exit 1 + fi + local project_name="${CRAFT_PROJECT_NAME:-"${SNAPCRAFT_PROJECT_NAME}"}" + + mapfile \ + -t all_release_tags \ + < <( + vcs_query_release_tags \ + "${PWD}" \ + "${tag_pattern_release}" + ) + + if ! { + test -v CRAFT_PROJECT_DIR \ + || test -v SNAPCRAFT_PROJECT_DIR + }; then + printf -- \ + "%s: Error: This script requires either the CRAFT_PROJECT_DIR or the SNAPCRAFT_PROJECT_DIR environment variable to be set.\\n" \ + "${script_name}" \ + >&2 + exit 1 + fi + project_dir="${CRAFT_PROJECT_DIR:-"${SNAPCRAFT_PROJECT_DIR}"}" + + printf -- \ + '%s: INFO: Determining normalized release version strings.\n' \ + "${script_name}" + determine_normalized_release_version_string \ + map_of_tag_to_normalized_version \ + "${all_release_tags[@]}" + + printf -- \ + '%s: INFO: Detecting stable releases...\n' \ + "${script_name}" + determine_stable_release_details \ + tag_pattern_stable \ + "${tag_pattern_release_candidate}" \ + "${tag_pattern_beta}" \ + last_stable_tag \ + last_stable_version \ + last_stable_version_on_the_snap_store \ + "${snap_version_postfix_seperator}" \ + "${flag_force_stable}" \ + "${all_release_tags[@]}" + + printf -- \ + '%s: INFO: Detecting release candidate releases...\n' \ + "${script_name}" + determine_release_candidate_release_details \ + "${tag_pattern_release_candidate}" \ + last_release_candidate_tag \ + last_release_candidate_version \ + last_release_candidate_version_on_the_snap_store \ + "${snap_version_postfix_seperator}" \ + "${all_release_tags[@]}" + + printf -- \ + '%s: INFO: Detecting beta releases...\n' \ + "${script_name}" + determine_beta_release_details \ + "${tag_pattern_beta}" \ + last_beta_tag \ + last_beta_version \ + last_beta_version_on_the_snap_store \ + "${snap_version_postfix_seperator}" \ + "${all_release_tags[@]}" + + local \ + selected_release_tag \ + selected_release_version \ + selected_snap_channel_version + release_type_to_build="$( + determine_which_version_to_build \ + "${last_stable_tag}" \ + "${last_stable_version}" \ + "${last_stable_version_on_the_snap_store}" \ + "${last_release_candidate_tag}" \ + "${last_release_candidate_version}" \ + "${last_release_candidate_version_on_the_snap_store}" \ + "${last_beta_tag}" \ + "${last_beta_version}" \ + "${last_beta_version_on_the_snap_store}" \ + "${flag_force_stable}" \ + "${flag_force_snapshot}" + )" + + + case "${release_type_to_build}" in + stable) + selected_release_tag="${last_stable_tag}" + selected_release_version="${last_stable_version}" + selected_snap_channel_version="${last_stable_version_on_the_snap_store}" + ;; + release-candidate) + selected_release_tag="${last_release_candidate_tag}" + selected_release_version="${last_release_candidate_version}" + selected_snap_channel_version="${last_release_candidate_version_on_the_snap_store}" + ;; + beta) + selected_release_tag="${last_beta_tag}" + selected_release_version="${last_beta_version}" + selected_snap_channel_version="${last_beta_version_on_the_snap_store}" + ;; + development-snapshot) + : # Nothing to do here + ;; + *) + printf -- \ + '%s: %s: FATAL: Invalid release_type_to_build(%s), report bug.\n' \ + "${script_name}" \ + "${FUNCNAME[0]}" \ + "${release_type_to_build}" \ + 1>&2 + exit 1 + ;; + esac + + unset \ + flag_version_mismatch_stable \ + flag_version_mismatch_beta \ + flag_version_mismatch_release_candidate \ + flag_force_stable \ + last_stable_tag \ + last_stable_version \ + last_stable_version_on_the_snap_store \ + last_release_candidate_tag \ + last_release_candidate_version \ + last_release_candidate_version_on_the_snap_store \ + last_beta_tag \ + last_beta_version \ + last_beta_version_on_the_snap_store + + if test "${release_type_to_build}" != development-snapshot; then + printf -- \ + "%s: Info: The last tagged %s release(%s) hasn't been promoted to the %s channel(%s) on the Snap Store yet, checking out %s.\\n" \ + "${script_name}" \ + "${release_type_to_build}" \ + "${selected_release_version}" \ + "${map_of_release_type_to_snap_channel["${release_type_to_build}"]}" \ + "${selected_snap_channel_version}" \ + "${selected_release_version}" + checkout_mode=release + else + printf -- '%s: Info: Last tagged releases is all in their respective channels, building development snapshot\n' \ + "${script_name}" + checkout_mode=snapshot + fi + + unset \ + all_release_tags \ + map_of_release_type_to_snap_channel \ + release_type_to_build \ + selected_release_version \ + selected_snap_channel_version + fi + + unset \ + tag_pattern_release + + case "${checkout_mode}" in + snapshot) + : # do nothing + ;; + release) + if test "${flag_dry_run}" == true; then + printf -- \ + '%s: Info: Would check out "%s" tag.\n' \ + "${script_name}" \ + "${selected_release_tag}" + else + vcs_checkout_tag \ + "${PWD}" \ + "${selected_release_tag}" + fi + ;; + *) + printf -- \ + '%s: Error: Invalid checkout_mode selected.\n' \ + "${script_name}" \ + >&2 + exit 1 + ;; + esac + + unset \ + checkout_mode \ + selected_release_tag + + upstream_version="$( + vcs_describe_version \ + "${PWD}" \ + "${revision_minimal_length_upstream}" \ + "${postfix_dirty_marker_upstream}" \ + | normalize_version + )" + + if test "${flag_append_packaging_version}" = true; then + packaging_revision="$( + vcs_describe_revision \ + "${project_dir}" \ + "${revision_minimal_length_packaging}" \ + "${postfix_dirty_marker_packaging}" + )" + snap_version="${upstream_version}+pkg-${packaging_revision}" + unset \ + project_dir \ + packaging_revision \ + postfix_dirty_marker_packaging \ + revision_minimal_length_packaging + else + snap_version="${upstream_version}" + fi + unset \ + postfix_dirty_marker_upstream \ + revision_minimal_length_upstream \ + tag_prefix_release \ + upstream_version + + printf -- '%s: Info: Snap version determined to be "%s".\n' \ + "${script_name}" \ + "${snap_version}" + if test "${flag_dry_run}" = false; then + if command -v craftctl >/dev/null; then + if ! craftctl set version="${snap_version}"; then + printf \ + 'Error: Unable to set the snap version string.\n' \ + 1>&2 + exit 2 + fi + else + if ! snapcraftctl set-version "${snap_version}"; then + printf \ + 'Error: Unable to set the snap version string.\n' \ + 1>&2 + exit 2 + fi + fi + fi + exit 0 +} + +determining_runtime_parameters(){ + local -n flag_append_packaging_version_ref="${1}"; shift + local -n tag_pattern_beta_ref="${1}"; shift + local -n flag_debug_tracing_ref="${1}"; shift + local -n flag_dry_run_ref="${1}"; shift + local -n flag_force_snapshot_ref="${1}"; shift + local -n flag_force_stable_ref="${1}"; shift + local -n postfix_dirty_marker_packaging_ref="${1}"; shift + local -n revision_minimal_length_packaging_ref="${1}"; shift + local -n tag_pattern_release_candidate_ref="${1}"; shift + local -n tag_pattern_release_ref="${1}"; shift + local -n tag_prefix_release_ref="${1}"; shift + local -n snap_version_postfix_seperator_ref="${1}"; shift + local -n tag_pattern_stable_ref="${1}"; shift + local -n postfix_dirty_marker_upstream_ref="${1}"; shift + local -n revision_minimal_length_upstream_ref="${1}"; shift + + while true; do + if test "${#}" -eq 0; then + break + else + case "${1}" in + # Append packaging revision after snap version + --append-packaging-revision) + # Indirect access + # shellcheck disable=SC2034 + flag_append_packaging_version_ref=true + ;; + --beta-tag-pattern*) + if test "${1}" != --beta-tag-pattern; then + # Indirect access + # shellcheck disable=SC2034 + tag_pattern_beta_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --beta-tag-pattern requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + tag_pattern_beta_ref="${2}" + shift 1 + fi + ;; + # Enable execution tracing + --debug) + printf -- \ + '%s: %s: Warning: The --debug command option is deprecated, set the SELECTIVE_CHECKOUT_DEBUG environment variable to "true" for a better debugging experience.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + export SELECTIVE_CHECKOUT_DEBUG=true + ;; + --debug-tracing) + # Indirect access + # shellcheck disable=SC2034 + flag_debug_tracing_ref=true + ;; + # Don't run snapcraftctl for testing purpose + --dry-run) + # Indirect access + # shellcheck disable=SC2034 + flag_dry_run_ref=true + ;; + # Force building development snapshot regardless the status of the snap + --force-snapshot) + # Indirect access + # shellcheck disable=SC2034 + flag_force_snapshot_ref=true + ;; + --force-stable) + # Indirect access + # shellcheck disable=SC2034 + flag_force_stable_ref=true + ;; + --packaging-dirty-marker-postfix*) + if test "${1}" != --packaging-dirty-marker-postfix; then + # Indirect access + # shellcheck disable=SC2034 + postfix_dirty_marker_packaging_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --packaging-dirty-marker-postfix requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + postfix_dirty_marker_packaging_ref="${2}" + shift 1 + fi + ;; + --packaging-revision-minimal-length*) + if test "${1}" != --packaging-revision-minimal-length; then + # Indirect access + # shellcheck disable=SC2034 + revision_minimal_length_packaging_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --packaging-revision-minimal-length requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + revision_minimal_length_packaging_ref="${2}" + shift 1 + fi + ;; + --release-candidate-tag-pattern*) + if test "${1}" != --release-candidate-tag-pattern; then + # Indirect access + # shellcheck disable=SC2034 + tag_pattern_release_candidate_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --release-candidate-tag-pattern requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + tag_pattern_release_candidate_ref="${2}" + shift 1 + fi + ;; + --release-tag-pattern*) + if test "${1}" != --release-tag-pattern; then + # Indirect access + # shellcheck disable=SC2034 + tag_pattern_release_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --release-tag-pattern requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + tag_pattern_release_ref="${2}" + shift 1 + fi + ;; + # Set the prefix for all release tags(default: `v`), the prefix will be stripped from snap version string + --release-tag-prefix*) + if test "${1}" != --release-tag-prefix; then + # Indirect access + # shellcheck disable=SC2034 + tag_prefix_release_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --release-tag-prefix requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + tag_prefix_release_ref="${2}" + shift 1 + fi + ;; + # Set the seperator for the postfixed string in the snap version string + # the postfixed string will be stripped before comparing with the stripped + # uptream release version + --snap-postfix-seperator*) + if test "${1}" != --snap-postfix-seperator; then + # Indirect access + # shellcheck disable=SC2034 + snap_version_postfix_seperator_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --snap-postfix-seperator requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + snap_version_postfix_seperator_ref="${2}" + shift 1 + fi + ;; + --stable-tag-pattern*) + if test "${1}" != --stable-tag-pattern; then + # Indirect access + # shellcheck disable=SC2034 + tag_pattern_stable_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --stable-tag-pattern requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + tag_pattern_stable_ref="${2}" + shift 1 + fi + ;; + --upstream-dirty-marker-postfix*) + if test "${1}" != --upstream-dirty-marker-postfix; then + # Indirect access + # shellcheck disable=SC2034 + postfix_dirty_marker_upstream_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --upstream-dirty-marker-postfix requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + postfix_dirty_marker_upstream_ref="${2}" + shift 1 + fi + ;; + --upstream-revision-minimal-length*) + if test "${1}" != --upstream-revision-minimal-length; then + # Indirect access + # shellcheck disable=SC2034 + revision_minimal_length_upstream_ref="$( + cut \ + --delimiter== \ + --fields=2 \ + <<< "${1}" + )" + else + if test "${#}" -eq 0; then + printf -- \ + '%s: %s: Error: --upstream-revision-minimal-length requires one argument.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + # Indirect access + # shellcheck disable=SC2034 + revision_minimal_length_upstream_ref="${2}" + shift 1 + fi + ;; + *) + printf -- \ + '%s: %s: Error: Invalid command-line argument "%s".\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${1}" \ + >&2 + return 1 + ;; + esac + shift 1 + fi + done +} + +normalize_version(){ + # This is not a simple substitution + # shellcheck disable=SC2001 + sed "s#^${tag_prefix_release}##" \ + | tr _ . +} + +determine_normalized_release_version_string(){ + local -n map_of_tag_to_normalized_version_ref="${1}"; shift + local -a all_release_tags=("${@}"); set -- + + local normalized_release_version + for tag in "${all_release_tags[@]}"; do + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: %s: DEBUG: Found release tag: %s\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${tag}" \ + 1>&2 + fi + normalized_release_version="$( + normalize_version <<< "${tag}" + )" + + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: %s: DEBUG: Normalized release version: %s\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${normalized_release_version}" \ + 1>&2 + fi + # Indirection + # shellcheck disable=SC2034 + map_of_tag_to_normalized_version_ref["${tag}"]="${normalized_release_version}" + done + unset \ + normalized_release_version \ + tag +} + +determine_stable_release_details(){ + # Indirection + # shellcheck disable=SC2034 + local -n tag_pattern_stable_ref="${1}"; shift + local tag_pattern_release_candidate="${1}"; shift + local tag_pattern_beta="${1}"; shift + local -n last_stable_tag_ref="${1}"; shift + local -n last_stable_version_ref="${1}"; shift + local -n last_stable_version_on_the_snap_store_ref="${1}"; shift + local snap_version_postfix_seperator="${1}"; shift + local flag_force_stable="${1}"; shift + local -a all_release_tags=("${@}"); set -- + + local stable_tag_pattern_grep_opts + if test -v tag_pattern_stable_ref; then + stable_tag_pattern_grep_opts= + else + stable_tag_pattern_grep_opts=--invert-match + tag_pattern_stable_ref="${tag_pattern_beta}|${tag_pattern_release_candidate}" + fi + + local -a stable_tags=() + mapfile \ + -t stable_tags \ + < <( + echo -n "${all_release_tags[@]}" \ + | tr ' ' "\\n" \ + | grep \ + --extended-regexp \ + ${stable_tag_pattern_grep_opts} \ + --regexp="(${tag_pattern_stable_ref})" + ) + + if test "${#stable_tags[@]}" -eq 0; then + last_stable_tag_ref= + last_stable_version_ref= + + # NOTE: The store CAN have releases, though... + last_stable_version_on_the_snap_store_ref= + else + last_stable_tag_ref="$( + echo -n "${stable_tags[@]}" \ + | tr ' ' "\\n" \ + | sort --version-sort \ + | tail --lines=1 + + )" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: %s: DEBUG: Last stable release tag determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${last_stable_tag_ref}" \ + 1>&2 + fi + + last_stable_version_ref="${map_of_tag_to_normalized_version["${last_stable_tag}"]}" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: %s: DEBUG: Last stable version determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${last_stable_version_ref}" \ + 1>&2 + fi + + last_stable_version_on_the_snap_store_ref="$( + snap_query_version \ + "${project_name}" \ + stable \ + "${snap_version_postfix_seperator}" + )" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: %s: DEBUG: Last stable version on the snap store determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${last_stable_version_on_the_snap_store_ref}" \ + 1>&2 + fi + fi + unset \ + stable_tag_pattern_grep_opts \ + tag_pattern_stable +} + +determine_release_candidate_release_details(){ + local tag_pattern_release_candidate="${1}"; shift + local -n last_release_candidate_tag_ref="${1}"; shift + local -n last_release_candidate_version_ref="${1}"; shift + local -n last_release_candidate_version_on_the_snap_store_ref="${1}"; shift + local snap_version_postfix_seperator="${1}"; shift + local -a all_release_tags=("${@}"); set -- + + local -a release_candidate_tags + mapfile \ + -t release_candidate_tags \ + < <( + echo -n "${all_release_tags[@]}" \ + | tr ' ' "\\n" \ + | grep \ + --extended-regexp \ + --regexp="${tag_pattern_release_candidate}" + ) + + if test "${#release_candidate_tags[@]}" -eq 0; then + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: DEBUG: No release candidate release tags found.\n' \ + "${SCRIPT_NAME}" \ + 1>&2 + fi + last_release_candidate_tag_ref= + last_release_candidate_version_ref= + last_release_candidate_version_on_the_snap_store_ref= + else + last_release_candidate_tag_ref="$( + echo -n "${release_candidate_tags[@]}" \ + | tr ' ' "\\n" \ + | grep \ + --extended-regexp \ + --regexp="${tag_pattern_release_candidate}" \ + | sort --version-sort \ + | tail --lines=1 + )" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: DEBUG: Last release candidate release tag determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${last_release_candidate_tag_ref}" \ + 1>&2 + fi + + last_release_candidate_version_ref="${map_of_tag_to_normalized_version["${last_release_candidate_tag_ref}"]}" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: DEBUG: Last release candidate version determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${last_release_candidate_version_ref}" \ + 1>&2 + fi + + last_release_candidate_version_on_the_snap_store_ref="$( + snap_query_version \ + "${project_name}" \ + candidate \ + "${snap_version_postfix_seperator}" + )" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: DEBUG: Last release candidate version on the snap store determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${last_release_candidate_version_on_the_snap_store_ref}" \ + 1>&2 + fi + fi + unset \ + tag_pattern_release_candidate +} + +determine_beta_release_details(){ + local tag_pattern_beta="${1}"; shift + local -n last_beta_tag_ref="${1}"; shift + local -n last_beta_version_ref="${1}"; shift + local -n last_beta_version_on_the_snap_store_ref="${1}"; shift + local snap_version_postfix_seperator="${1}"; shift + local all_release_tags=("${@}"); set -- + + local -a beta_tags + mapfile \ + -t beta_tags \ + < <( + echo -n "${all_release_tags[@]}" \ + | tr ' ' "\\n" \ + | grep \ + --extended-regexp \ + --regexp="${tag_pattern_beta}" + ) + + if test "${#beta_tags[@]}" -eq 0; then + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: DEBUG: No beta release tags found.\n' \ + "${SCRIPT_NAME}" \ + 1>&2 + fi + last_beta_tag_ref= + last_beta_version_ref= + last_beta_version_on_the_snap_store= + else + last_beta_tag_ref="$( + echo -n "${beta_tags[@]}" \ + | tr ' ' "\\n" \ + | grep \ + --extended-regexp \ + --regexp="${tag_pattern_beta}" \ + | sort --version-sort \ + | tail --lines=1 + )" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: DEBUG: Last beta release tag determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${last_beta_tag_ref}" \ + 1>&2 + fi + + last_beta_version_ref="${map_of_tag_to_normalized_version["${last_beta_tag_ref}"]}" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: DEBUG: Last beta version determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${last_beta_version_ref}" \ + 1>&2 + fi + + last_beta_version_on_the_snap_store="$( + snap_query_version \ + "${project_name}" \ + beta \ + "${snap_version_postfix_seperator}" + )" + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: DEBUG: Last beta version on the snap store determines to be "%s".\n' \ + "${SCRIPT_NAME}" \ + "${last_beta_version_on_the_snap_store_ref}" \ + 1>&2 + fi + fi + unset \ + map_of_tag_to_normalized_version \ + snap_version_postfix_seperator \ + tag_pattern_beta +} + +determine_which_version_to_build(){ + local last_stable_tag="${1}"; shift + local last_stable_version="${1}"; shift + local last_stable_version_on_the_snap_store="${1}"; shift + local last_release_candidate_tag="${1}"; shift + local last_release_candidate_version="${1}"; shift + local last_release_candidate_version_on_the_snap_store="${1}"; shift + local last_beta_tag="${1}"; shift + local last_beta_version="${1}"; shift + local last_beta_version_on_the_snap_store="${1}"; shift + local flag_force_stable="${1}"; shift + local flag_force_snapshot="${1}"; shift + + local release_type_to_build=undetermined + + # Case: --force-snapshot is specified + if test "${flag_force_snapshot}" == true; then + printf -- \ + '%s: %s: DEBUG: The --force-snapshot command option is specified, development snapshot will be built.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + 1>&2 + release_type_to_build=development-snapshot + # Case: Build the stable version when: + # + # --force-stable command-line option is specified + # AND There is stable release version to build on + elif test "${flag_force_stable}" == true; then + if test -z "${last_stable_version}"; then + printf -- \ + '%s: %s: Error: The --force-stable command-line option is specified, but no stable versions are found.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + 1>&2 + return 1 + fi + + printf -- \ + '%s: %s: DEBUG: The --force-stable command-line option is specified, stable release will be built.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + 1>&2 + release_type_to_build=stable + # Case: Build the stable version when: + # + # The stable version is available + # AND ( + # There's no snap available in the stable channel of the Snap + # Store + # OR The stable version is newer than the one on the stable + # channel of the snap store + # ) + elif test -n "${last_stable_version}" \ + && { + test -z "${last_stable_version_on_the_snap_store}" \ + || version_former_is_greater_than_latter \ + "${last_stable_version}" \ + "${last_stable_version_on_the_snap_store}" + }; then + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: %s: DEBUG: Stable version(%s) is newer than the one on the Snap Store(%s), stable release will be built.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${last_stable_version}" \ + "${last_stable_version_on_the_snap_store:-none}" \ + 1>&2 + fi + release_type_to_build=stable + # Case: Build release candidate version when: + # + # Release candidate version is available + # AND ( + # There's no snap on the Snap Store's stable channel + # OR The release candidate version is newer than the version + # on the Snap Store's stable channel + # ) + # AND ( + # There's no release in the Snap Store's candidate channel + # OR The release candidate version is newer than the version + # in the Snap Store's candidate channel + # ) + elif test -n "${last_release_candidate_version}" \ + && { + test -z "${last_stable_version_on_the_snap_store}" \ + || version_former_is_greater_than_latter \ + "${last_release_candidate_version}" \ + "${last_stable_version_on_the_snap_store}" + } && { + test -z "${last_release_candidate_version_on_the_snap_store}" \ + || version_former_is_greater_than_latter \ + "${last_release_candidate_version}" \ + "${last_release_candidate_version_on_the_snap_store}" + }; then + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + "%s: %s: DEBUG: Release candidate version(%s) is not on the Snap Store's candidate channel and is newer than the version on the stable channel(%s), release candidate version will be built.\\n" \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${last_release_candidate_version}" \ + "${last_stable_version_on_the_snap_store:-none}" \ + 1>&2 + fi + release_type_to_build=release-candidate + # Case: Build beta version when + # + # The beta version is available + # AND ( + # There's NO snap on the stable channel of the Snap Store + # OR The beta version is newer than the stable version on + # the Snap Store + # ) + # AND ( + # There's NO snap on the candidate channel of the Snap Store + # OR The beta version is newer than the candidate version + # on the Snap Store + # ) + # AND ( + # There's NO snap on the beta channel of the Snap Store + # OR The beta version is newer than the beta version on + # the Snap Store + # ) + elif test -n "${last_beta_version}" \ + && { + test -z "${last_stable_version_on_the_snap_store}" \ + || version_former_is_greater_than_latter \ + "${last_beta_version}" \ + "${last_stable_version_on_the_snap_store}" + } && { + test -z "${last_release_candidate_version_on_the_snap_store}" \ + || version_former_is_greater_than_latter \ + "${last_beta_version}" \ + "${last_release_candidate_version_on_the_snap_store}" + } && { + test -z "${last_beta_version_on_the_snap_store}" \ + || version_former_is_greater_than_latter \ + "${last_beta_version}" \ + "${last_beta_version_on_the_snap_store}" + }; then + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + "%s: %s: DEBUG: Beta version(%s) is newer than the versions in the stabler channels(stable(%s), candidate(%s)), and is newer than the one shipped in the Snap Store's beta channel(%s), beta version will be built.\\n" \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${last_beta_version}" \ + "${last_stable_version_on_the_snap_store:-none}" \ + "${last_release_candidate_version_on_the_snap_store:-none}" \ + "${last_beta_version_on_the_snap_store:-none}" \ + 1>&2 + fi + release_type_to_build=beta + # Case: Build development snapshot when: + # + # All other versions are built + else + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + "%s: %s: DEBUG: All other versions are on the Snap Store(stable(%s), candidate(%s), beta(%s)), development snapshot will be built.\\n" \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${last_stable_version_on_the_snap_store:-none}" \ + "${last_release_candidate_version_on_the_snap_store:-none}" \ + "${last_beta_version_on_the_snap_store:-none}" \ + 1>&2 + fi + release_type_to_build=development-snapshot + fi + + printf \ + '%s' \ + "${release_type_to_build}" +} + +# Query snap version for specific channel, we use the Snap Store API +# Output: snap version string, or null string if the channel has no +# snap +# http://api.snapcraft.io/docs/info.html#snap_info +snap_query_version(){ + if test $# -ne 3; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + # positional parameters + local snap_identifier="${1}"; shift + local release_channel="${1}"; shift + local snap_version_postfix_seperator="${1}"; shift + + snap_arch="${SNAP_ARCH:-amd64}" + + local snap_version_in_release_channel + local info_store_api_call_response + + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + '%s: %s: DEBUG: Checking what snap revisions are available for the %s snap at the %s release channel...\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${snap_identifier}" \ + "${release_channel}" \ + 1>&2 + fi + if ! info_store_api_call_response="$( + curl \ + --fail \ + --silent \ + --show-error \ + --header 'Snap-Device-Series: 16' \ + "https://api.snapcraft.io/v2/snaps/info/${snap_identifier}?fields=version&architecture=${snap_arch}" \ + 2>&1 + )"; then + # If the response is 404, the snap hasn't published to the + # the specified channel(or, at all) at the Snap Store yet + regex_curl_request_returns_404='\(22\).*: 404$' + grep_opts=( + --extended-regexp + --regexp="${regex_curl_request_returns_404}" + --quiet + ) + if grep "${grep_opts[@]}" <<<"${info_store_api_call_response}"; then + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf -- \ + "%s: %s: DEBUG: The /snaps/info/ Snap Store API call returns 404, interpreting as the snap hasn't published to the store...\\n" \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + 1>&2 + fi + return 0 + fi + + printf \ + '%s: %s: Error: Unable to call the Snap Store info API to retrieve snap revision info: %s\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${info_store_api_call_response}" \ + 1>&2 + return 1 + fi + + if ! snap_version_in_release_channel="$( + jq \ + --raw-output \ + ".\"channel-map\"[] + | select( + .channel.name == \"${release_channel}\" + ).version" \ + <<< "${info_store_api_call_response}" \ + 2>&1 + )"; then + printf \ + '%s: %s: Error: Unable to query the snap version from the store info API response: %s\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${snap_version_in_release_channel}" \ + 1>&2 + return 2 + fi + if test -z "${snap_version_in_release_channel}"; then + # No snaps available in the channel + return 0 + fi + if test "${SELECTIVE_CHECKOUT_DEBUG}" == true; then + printf \ + '%s: %s: DEBUG: Snap version determined to be: "%s".\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + "${snap_version_in_release_channel}" \ + 1>&2 + fi + + printf %s "${snap_version_in_release_channel}" +} + +# Determine which VCS is used +# FIXME: Allow specifying any node under the working directory, not just the root node +vcs_detect(){ + if test $# -ne 1; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local source_tree_root_dir="${1}"; shift 1 + + if test -e "${source_tree_root_dir}"/.git; then + printf git + elif test -e "${source_tree_root_dir}"/.hg; then + printf mercurial + elif test -e "${source_tree_root_dir}"/.svn; then + printf subversion + else + printf not_found + fi +} + +# Ensure depending software is available before using them +vcs_check_runtime_dependencies(){ + if test $# -ne 1; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local -r source_tree_root_dir="${1}"; shift 1 + + case "$(vcs_detect "${source_tree_root_dir}")" in + git) + if ! command -v git &>/dev/null; then + # Markdown code markup is not Bash tilde expression + # shellcheck disable=SC2016 + printf -- \ + '%s: %s: Error: `git` command not found in the command search PATHs.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + ;; + mercurial) + if ! command -v hg &>/dev/null; then + # Markdown code markup is not Bash tilde expression + # shellcheck disable=SC2016 + printf -- \ + '%s: %s: Error: `hg` command not found in the command search PATHs.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + ;; + subversion) + if ! command -v svn &>/dev/null; then + # Markdown code markup is not Bash tilde expression + # shellcheck disable=SC2016 + printf -- \ + '%s: %s: Error: `svn` command not found in the command search PATHs.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + fi + ;; + *) + printf -- \ + '%s: %s: Warning: Unknown VCS type, assuming none.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 0 + ;; + esac +} + +vcs_is_dirty(){ + if test $# -ne 1; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local -r source_tree_root_dir="${1}"; shift 1 + + case "$(vcs_detect "${source_tree_root_dir}")" in + git) + # If tracked files are modified + # or staging area not empty + if ! \ + git -C "${source_tree_root_dir}" diff \ + --quiet \ + || ! \ + git -C "${source_tree_root_dir}" diff \ + --staged \ + --quiet; then + return 0 + else + return 1 + fi + ;; + mercurial) + # NOTE: + # The existence of untracked files is not consider dirty, + # imitating Git's `--dirty` option of the describe + # subcommand + if test -n "$( + hg --cwd "${source_tree_root_dir}" status \ + --added \ + --deleted \ + --modified \ + --removed + )"; then + return 0 + else + return 1 + fi + ;; + subversion) + # NOTE: Is there any straightforward way to check this? + if test -n "$( + svn status -q + )"; then + return 0 + else + return 1 + fi + ;; + *) + printf -- \ + '%s: %s: Warning: Unknown VCS type, assuming dirty.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + return 0 + ;; + esac +} + +vcs_has_release_tags() { + if test $# -ne 2; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local -r source_tree_root_dir="${1}"; shift 1 + + # Pattern to match a release tag, in extended regular expression(ERE) + local -r tag_pattern_release="${1}"; shift 1 + + case "$(vcs_detect "${source_tree_root_dir}")" in + git) + if git -C "${source_tree_root_dir}" tag \ + --list \ + | grep \ + --extended-regexp \ + --quiet \ + --regexp="${tag_pattern_release}"; then + return 0 + else + return 1 + fi + ;; + mercurial) + if hg --cwd "${source_tree_root_dir}" tags \ + | grep \ + --extended-regexp \ + --quiet \ + --regexp="${tag_pattern_release}"; then + return 0 + else + return 1 + fi + ;; + subversion) + local \ + source_tree_root_url \ + tags_dir_url + + source_tree_root_url="$( + svn info \ + --show-item url \ + "${source_tree_root_dir}" + )" + + # Supported source URLs: + # * /trunk + # * /tags/_tag_name_ + case "${source_tree_root_url}" in + */trunk) + # Strip trailing shortest matched pattern + # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + tags_dir_url="${source_tree_root_url%/trunk}/tags" + ;; + */tags/*) + tags_dir_url="${source_tree_root_url%/*}" + ;; + *) + printf -- \ + 'Warning: %s: Unsupported SVN source URL, assuming no release tags found.\n' \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + ;; + esac + + if svn list \ + "${tags_dir_url}" \ + | sed 's#/$##' \ + | grep \ + --extended-regexp \ + --quiet \ + --regexp="${tag_pattern_release}"; then + return 0 + else + return 1 + fi + ;; + *) + printf -- \ + '%s: Warning: Unknown VCS type, assuming no release tags found.\n' \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + ;; + esac +} + +vcs_query_release_tags(){ + if test $# -ne 2; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local -r source_tree_root_dir="${1}"; shift 1 + + # Pattern to match a release tag, in extended regular expression(ERE) + local -r tag_pattern_release="${1}"; shift 1 + + case "$(vcs_detect "${source_tree_root_dir}")" in + git) + if git -C "${source_tree_root_dir}" tag \ + --list \ + | grep \ + --extended-regexp \ + --regexp="${tag_pattern_release}"; then + return 0 + else + return 1 + fi + ;; + mercurial) + if hg --cwd "${source_tree_root_dir}" tags \ + --quiet \ + | grep \ + --extended-regexp \ + --regexp="${tag_pattern_release}"; then + return 0 + else + return 1 + fi + ;; + subversion) + local \ + source_tree_root_url \ + tags_dir_url + + source_tree_root_url="$( + svn info \ + --show-item url \ + "${source_tree_root_dir}" + )" + + # Supported source URLs: + # * /trunk + # * /tags/_tag_name_ + case "${source_tree_root_url}" in + */trunk) + # Strip trailing shortest matched pattern + # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + tags_dir_url="${source_tree_root_url%/trunk}/tags" + ;; + */tags/*) + tags_dir_url="${source_tree_root_url%/*}" + ;; + *) + printf -- \ + 'Warning: %s: Unsupported SVN source URL, assuming no release tags found.\n' \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + ;; + esac + + if svn list \ + "${tags_dir_url}" \ + | sed 's#/$##' \ + | grep \ + --extended-regexp \ + --regexp="${tag_pattern_release}"; then + return 0 + else + return 1 + fi + ;; + *) + printf -- \ + '%s: Warning: Unknown VCS type, assuming no release tags found.\n' \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + ;; + esac +} + +vcs_checkout_tag(){ + if test $# -ne 2; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local -r source_tree_root_dir="${1}"; shift 1 + local -r tag_to_be_checked_out="${1}"; shift 1 + + case "$(vcs_detect "${source_tree_root_dir}")" in + git) + git -C "${source_tree_root_dir}" checkout \ + "${tag_to_be_checked_out}" + return 0 + ;; + mercurial) + hg --cwd "${source_tree_root_dir}" checkout \ + --rev "${tag_to_be_checked_out}" + return 0 + ;; + subversion) + local \ + source_tree_root_url \ + tags_dir_url + + source_tree_root_url="$( + svn info \ + --show-item url \ + "${source_tree_root_dir}" + )" + + # Supported source URLs: + # * /trunk + # * /tags/_tag_name_ + case "${source_tree_root_url}" in + */trunk) + # Strip trailing shortest matched pattern + # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + tags_dir_url="${source_tree_root_url%/trunk}/tags" + ;; + */tags/*) + tags_dir_url="${source_tree_root_url%/*}" + ;; + *) + printf -- \ + 'Warning: %s: Unsupported SVN source URL, assuming check out failed.\n' \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + ;; + esac + + if svn checkout \ + "${tags_dir_url}"/"${tag_to_be_checked_out}"; then + return 0 + else + return 1 + fi + ;; + *) + printf -- \ + '%s: Warning: Unknown VCS type, not doing anything.\n' \ + "${FUNCNAME[0]}" \ + >&2 + return 1 + ;; + esac +} + +# Describe software version, use tags when available +vcs_describe_version(){ + if test $# -ne 3; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local -r source_tree_root_dir="${1}"; shift 1 + local -r revision_identifier_length_minimum="${1}"; shift 1 + local -r dirty_postfix="${1}"; shift 1 + + case "$(vcs_detect "${source_tree_root_dir}")" in + git) + git describe \ + --abbrev="${revision_identifier_length_minimum}" \ + --always \ + --dirty="${dirty_postfix}" \ + --tags + return 0 + ;; + mercurial) + hg --cwd "${source_tree_root_dir}" log \ + --rev . \ + --template "{latesttag}{sub('^-0-.*', '', '-{latesttagdistance}-m{shortest(node, ${revision_identifier_length_minimum})}')}" + if vcs_is_dirty "${source_tree_root_dir}"; then + printf -- %s "${dirty_postfix}" + fi + return 0 + ;; + subversion) + local \ + source_tree_root_url + + source_tree_root_url="$( + svn info \ + --show-item url \ + "${source_tree_root_dir}" + )" + + # Supported source URLs: + # * /trunk + # * /tags/_tag_name_ + case "${source_tree_root_url}" in + */trunk) + printf %s \ + "rev$( + svn info \ + --show-item revision \ + "${source_tree_root_dir}" + )" + ;; + */tags/*) + local tag + tag="${source_tree_root_url##*/}" + printf %s "${tag}" + ;; + *) + printf -- \ + 'Warning: %s: Unsupported SVN source URL.\n' \ + "${FUNCNAME[0]}" \ + >&2 + printf unknown + return 1 + ;; + esac + + if vcs_is_dirty "${source_tree_root_dir}"; then + printf -- %s "${dirty_postfix}" + fi + ;; + *) + printf -- \ + '%s: Warning: Unknown VCS type.\n' \ + "${FUNCNAME[0]}" \ + >&2 + printf unknown + return 0 + ;; + esac +} + +# Describe version control revision, only revision identifier/hash with +# customizable minimum length +# FIXME: Some node among source tree should be enough for source_tree_root_dir +vcs_describe_revision(){ + if test $# -ne 3; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local -r source_tree_root_dir="${1}"; shift 1 + local -r revision_identifier_length_minimum="${1}"; shift 1 + local -r dirty_postfix="${1}"; shift 1 + + case "$(vcs_detect "${source_tree_root_dir}")" in + git) + if ! git -C "${source_tree_root_dir}" describe --always >/dev/null; then + printf unknown + else + git -C "${source_tree_root_dir}" describe \ + --abbrev="${revision_identifier_length_minimum}" \ + --always \ + --dirty="${dirty_postfix}" \ + --match=nothing + fi + return 0 + ;; + mercurial) + if ! hg --cwd "${source_tree_root_dir}" status >/dev/null; then + printf unknown + else + # FIXME: Is there a better way of generating this only using Mercurial? + local hg_revision + + hg_revision+="$( + hg --cwd "${source_tree_root_dir}" log \ + --rev . \ + --template "{shortest(node, ${revision_identifier_length_minimum})}" + )" + + if vcs_is_dirty \ + "${SCRIPT_NAME}" \ + "${source_tree_root_dir}"; then + hg_revision+="${dirty_postfix}" + fi + + printf -- %s "${hg_revision}" + fi + return 0 + ;; + subversion) + svn info \ + --show-item revision \ + "${source_tree_root_dir}" + + if vcs_is_dirty \ + "${SCRIPT_NAME}" \ + "${source_tree_root_dir}"; then + printf -- %s "${dirty_postfix}" + fi + ;; + *) + printf -- \ + '%s: %s: Warning: Unknown VCS type.\n' \ + "${SCRIPT_NAME}" \ + "${FUNCNAME[0]}" \ + >&2 + printf unknown + return 0 + ;; + esac +} + +# Check whether a version string is newer than the other version string +# identical version is considered "not newer" +version_former_is_greater_than_latter(){ + if test $# -ne 2; then + printf 'FATAL: %s: Parameter quantity mismatch.\n' "${FUNCNAME[0]}" >&2 + exit 1 + fi + + local -r former="${1}"; shift + local -r latter="${1}"; shift + + if test "${former}" = "${latter}"; then + return 1 + fi + + local newer_version + newer_version="$( + printf \ + -- \ + '%s\n%s' \ + "${former}" \ + "${latter}" \ + | sort \ + --version-sort \ + --reverse \ + | head \ + --lines=1 + )" + + if test "${newer_version}" = "${former}"; then + return 0 + else + return 1 + fi +} + +init "${@}" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a0688cb7e2..a419137fb8 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: tesseract -version: git +adopt-info: tesseract summary: open source optical character recognition engine description: | Tesseract has unicode (UTF-8) support, and can recognize more than 100 @@ -28,10 +28,19 @@ parts: tesseract: after: [leptonica] source: . + override-pull: | + craftctl default + "${CRAFT_PROJECT_DIR}/snap/local/selective-checkout" \ + --beta-tag-pattern='-beta-[[:digit:]]+$' plugin: autotools build-packages: - pkg-config - libtiff-dev + + # Dependencies of the selective-checkout scriptlet + - curl + - git + - jq stage-packages: - libgomp1 - libtiff5 From 97175554214694294d1fa823e29336eaace11f76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Fri, 9 Aug 2024 22:07:59 +0800 Subject: [PATCH 18/25] snap: Fix missing libarchive and libcurl support of the Tesseract part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index a419137fb8..934ca96c05 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -35,6 +35,8 @@ parts: plugin: autotools build-packages: - pkg-config + - libarchive-dev + - libcurl4-gnutls-dev - libtiff-dev # Dependencies of the selective-checkout scriptlet @@ -42,6 +44,8 @@ parts: - git - jq stage-packages: + - libarchive13 + - libcurl3-gnutls - libgomp1 - libtiff5 autotools-configure-parameters: @@ -50,6 +54,10 @@ parts: - --disable-doc - --disable-static - --disable-training + + # Enable core features + - --with-archive + - --with-curl prime: - -usr/local/include - -usr/local/lib/pkgconfig From 0331ab5f9694d62fef3bd841ec2d5e15f6e4c1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Sat, 10 Aug 2024 02:34:27 +0800 Subject: [PATCH 19/25] snap: Fix libraries under $SNAP/usr/local/lib aren't loaded in runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Libraries under $SNAP/usr/local/lib isn't searched by the dynamic linker by default. Refer-to: Staging - Build and staging dependencies - doc - snapcraft.io Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 934ca96c05..8cf20cdbf8 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -19,6 +19,7 @@ apps: tesseract: command: usr/local/bin/tesseract environment: + LD_LIBRARY_PATH: $LD_LIBRARY_PATH:$SNAP/usr/local/lib TESSDATA_PREFIX: $SNAP_USER_COMMON plugs: - home From b8b0fd0b065aa95a927622ac226b924a426e2531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Sat, 10 Aug 2024 02:39:51 +0800 Subject: [PATCH 20/25] snap: Strip unused icu libraries from snap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch addresses the following linter warnings: ``` - library: libicuio.so.70: unused library 'usr/lib/x86_64-linux-gnu/libicuio.so.70.1'. (https://snapcraft.io/docs/linters-library) - library: libicutest.so.70: unused library 'usr/lib/x86_64-linux-gnu/libicutest.so.70.1'. (https://snapcraft.io/docs/linters-library) - library: libicutu.so.70: unused library 'usr/lib/x86_64-linux-gnu/libicutu.so.70.1'. (https://snapcraft.io/docs/linters-library - library: libicui18n.so.70: unused library 'usr/lib/x86_64-linux-gnu/libicui18n.so.70.1'. (https://snapcraft.io/docs/linters-library) ``` Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 8cf20cdbf8..596616e19d 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -60,6 +60,10 @@ parts: - --with-archive - --with-curl prime: + - -usr/lib/$CRAFT_ARCH_TRIPLET/libicuio.so* + - -usr/lib/$CRAFT_ARCH_TRIPLET/libicui18n.so* + - -usr/lib/$CRAFT_ARCH_TRIPLET/libicutest.so* + - -usr/lib/$CRAFT_ARCH_TRIPLET/libicutu.so* - -usr/local/include - -usr/local/lib/pkgconfig - -usr/share/lintian From 3a2cbc1df2a5d702cc3061578d090e2582c90316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Sat, 10 Aug 2024 02:59:59 +0800 Subject: [PATCH 21/25] fixup! snap: Fix missing libarchive and libcurl support of the Tesseract part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libcurl functionality requires network access. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 596616e19d..46d279bac0 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -25,6 +25,9 @@ apps: - home - removable-media + # For recognition of remote images + - network + parts: tesseract: after: [leptonica] From bdccad53445d442ca83c9a0492c3acddf3bb4366 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Sun, 11 Aug 2024 01:33:47 +0800 Subject: [PATCH 22/25] snap: Implement fallback config loading mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes configuration files in the readonly snap filesystem not loadable by Tesseract due to missing datadir fallback logic. If the config file with the same name doesn't appear in the $SNAP_USER_COMMON directory, the one in the $SNAP/usr/local/share/tessdata directory will be loaded, allow Tesseract to work properly. Signed-off-by: 林博仁(Buo-ren Lin) --- .gitignore | 1 + .../implement-fallback-config-loading.patch | 53 +++++++++++++++++++ snap/snapcraft.yaml | 3 ++ 3 files changed, 57 insertions(+) create mode 100644 snap/local/implement-fallback-config-loading.patch diff --git a/.gitignore b/.gitignore index 81b9995a93..9735fc1580 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ config_auto.h /src/training/wordlist2dawg *.patch +!/snap/local/*.patch # files generated by libtool /src/training/combine_lang_model diff --git a/snap/local/implement-fallback-config-loading.patch b/snap/local/implement-fallback-config-loading.patch new file mode 100644 index 0000000000..49c9151585 --- /dev/null +++ b/snap/local/implement-fallback-config-loading.patch @@ -0,0 +1,53 @@ +Implement fallback config loading mechanism + +This patch fixes configuration files in the readonly snap filesystem +not loadable by Tesseract due to missing datadir fallback logic. + +If the config file with the same name doesn't appear in the +$SNAP_USER_COMMON directory, the one in the $SNAP/usr/local/share/tessdata +directory will be loaded, allow Tesseract to work properly. + +Signed-off-by: 林博仁(Buo-ren Lin) + +diff --git a/src/ccmain/tessedit.cpp b/src/ccmain/tessedit.cpp +index c7518883..b8a5cae6 100644 +--- a/src/ccmain/tessedit.cpp ++++ b/src/ccmain/tessedit.cpp +@@ -37,6 +37,7 @@ + # include "reject.h" + #endif + #include "lstmrecognizer.h" ++#include + + namespace tesseract { + +@@ -57,7 +58,28 @@ void Tesseract::read_config_file(const char *filename, SetParamConstraint constr + if ((fp = fopen(path.c_str(), "rb")) != nullptr) { + fclose(fp); + } else { +- path = filename; ++ const char *snap_env = getenv("SNAP"); ++ if (snap_env != nullptr) { ++ std::string snap_prefix = snap_env; ++ std::string datadir_readonly = snap_prefix += "/usr/local/share/tessdata/"; ++ path = datadir_readonly; ++ path += "configs/"; ++ path += filename; ++ if ((fp = fopen(path.c_str(), "rb")) != nullptr) { ++ fclose(fp); ++ } else { ++ path = datadir_readonly; ++ path += "tessconfigs/"; ++ path += filename; ++ if ((fp = fopen(path.c_str(), "rb")) != nullptr) { ++ fclose(fp); ++ } else { ++ path = filename; ++ } ++ } ++ } else { ++ path = filename; ++ } + } + } + ParamUtils::ReadParamsFile(path.c_str(), constraint, this->params()); diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 46d279bac0..0fe5b85eb5 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -62,6 +62,9 @@ parts: # Enable core features - --with-archive - --with-curl + override-build: | + patch -p1 < "${CRAFT_PROJECT_DIR}/snap/local/implement-fallback-config-loading.patch" + craftctl default prime: - -usr/lib/$CRAFT_ARCH_TRIPLET/libicuio.so* - -usr/lib/$CRAFT_ARCH_TRIPLET/libicui18n.so* From 32c43201b9313a91f253ce66e379d1a0ee6c746d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Sun, 11 Aug 2024 02:31:18 +0800 Subject: [PATCH 23/25] snap: Replace deprecated CRAFT_ARCH_TRIPLET Snapcraft part environment variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch addresses the following Snapcraft linter warnings: ``` CRAFT_ARCH_TRIPLET is deprecated, use CRAFT_ARCH_TRIPLET_BUILD_{ON|FOR} ``` Refer-to: core22 | Parts environment variables | Snapcraft documentation Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 0fe5b85eb5..9a9e9e1581 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -66,10 +66,10 @@ parts: patch -p1 < "${CRAFT_PROJECT_DIR}/snap/local/implement-fallback-config-loading.patch" craftctl default prime: - - -usr/lib/$CRAFT_ARCH_TRIPLET/libicuio.so* - - -usr/lib/$CRAFT_ARCH_TRIPLET/libicui18n.so* - - -usr/lib/$CRAFT_ARCH_TRIPLET/libicutest.so* - - -usr/lib/$CRAFT_ARCH_TRIPLET/libicutu.so* + - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libicuio.so* + - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libicui18n.so* + - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libicutest.so* + - -usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libicutu.so* - -usr/local/include - -usr/local/lib/pkgconfig - -usr/share/lintian @@ -110,6 +110,6 @@ parts: - --with-libwebp - --with-libopenjpeg prime: - - usr/lib/$CRAFT_ARCH_TRIPLET/*.so* + - usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/*.so* - usr/local/lib/*.so* - usr/share/doc/*/copyright* From 88c3e3360f3fac10bb3a51c85d3a27ea23b05caa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Sun, 11 Aug 2024 16:25:15 +0800 Subject: [PATCH 24/25] snap: Fix incorrect compiled-in datadir MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes the incorrect compiled-in datadir path and as a side-effect, simplifies the fallback config loading patch. Signed-off-by: 林博仁(Buo-ren Lin) --- .../implement-fallback-config-loading.patch | 34 ++++++++----------- snap/snapcraft.yaml | 3 ++ 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/snap/local/implement-fallback-config-loading.patch b/snap/local/implement-fallback-config-loading.patch index 49c9151585..5bd3769a48 100644 --- a/snap/local/implement-fallback-config-loading.patch +++ b/snap/local/implement-fallback-config-loading.patch @@ -3,14 +3,14 @@ Implement fallback config loading mechanism This patch fixes configuration files in the readonly snap filesystem not loadable by Tesseract due to missing datadir fallback logic. -If the config file with the same name doesn't appear in the -$SNAP_USER_COMMON directory, the one in the $SNAP/usr/local/share/tessdata -directory will be loaded, allow Tesseract to work properly. +If the config file with the same name doesn't exist in the +user-specified tessdata prefix directory, the one in the compiled-in +directory will be loaded, allows Tesseract to work properly. Signed-off-by: 林博仁(Buo-ren Lin) diff --git a/src/ccmain/tessedit.cpp b/src/ccmain/tessedit.cpp -index c7518883..b8a5cae6 100644 +index c7518883..d0031eb5 100644 --- a/src/ccmain/tessedit.cpp +++ b/src/ccmain/tessedit.cpp @@ -37,6 +37,7 @@ @@ -21,32 +21,26 @@ index c7518883..b8a5cae6 100644 namespace tesseract { -@@ -57,7 +58,28 @@ void Tesseract::read_config_file(const char *filename, SetParamConstraint constr +@@ -57,7 +58,22 @@ void Tesseract::read_config_file(const char *filename, SetParamConstraint constr if ((fp = fopen(path.c_str(), "rb")) != nullptr) { fclose(fp); } else { - path = filename; -+ const char *snap_env = getenv("SNAP"); -+ if (snap_env != nullptr) { -+ std::string snap_prefix = snap_env; -+ std::string datadir_readonly = snap_prefix += "/usr/local/share/tessdata/"; ++ std::string datadir_readonly = TESSDATA_PREFIX "/tessdata/"; ++ path = datadir_readonly; ++ path += "configs/"; ++ path += filename; ++ if ((fp = fopen(path.c_str(), "rb")) != nullptr) { ++ fclose(fp); ++ } else { + path = datadir_readonly; -+ path += "configs/"; ++ path += "tessconfigs/"; + path += filename; + if ((fp = fopen(path.c_str(), "rb")) != nullptr) { + fclose(fp); + } else { -+ path = datadir_readonly; -+ path += "tessconfigs/"; -+ path += filename; -+ if ((fp = fopen(path.c_str(), "rb")) != nullptr) { -+ fclose(fp); -+ } else { -+ path = filename; -+ } ++ path = filename; + } -+ } else { -+ path = filename; + } } } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9a9e9e1581..d9577e31c1 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -62,6 +62,9 @@ parts: # Enable core features - --with-archive - --with-curl + + # Fix incorrect compiled-in datadir + - CPPFLAGS=-DTESSDATA_PREFIX=\"\\\"/snap/$CRAFT_PROJECT_NAME/current/usr/local/share\\\"\" override-build: | patch -p1 < "${CRAFT_PROJECT_DIR}/snap/local/implement-fallback-config-loading.patch" craftctl default From ca4731567689b6d00e687de00d9fc1bd67c6e703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E5=8D=9A=E4=BB=81=28Buo-ren=20Lin=29?= Date: Sun, 11 Aug 2024 16:30:03 +0800 Subject: [PATCH 25/25] snap: Avoid hardcoding the TESSDATA_PREFIX environment variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch allows the user to store Tesseract data in custom paths using the TESSDATA_PREFIX environment variable. Previously it is hardcoded to $SNAP_USER_COMMON. Signed-off-by: 林博仁(Buo-ren Lin) --- snap/snapcraft.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d9577e31c1..bb9fa9c762 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -20,7 +20,6 @@ apps: command: usr/local/bin/tesseract environment: LD_LIBRARY_PATH: $LD_LIBRARY_PATH:$SNAP/usr/local/lib - TESSDATA_PREFIX: $SNAP_USER_COMMON plugs: - home - removable-media