From c72511e4c5ed72d9d31c21e054a6aaf002d71d4b Mon Sep 17 00:00:00 2001 From: davidlion Date: Mon, 25 Aug 2025 22:23:13 -0400 Subject: [PATCH 01/19] Improve checksum compute and validate error handling; add unit tests. --- exports/taskfiles/utils/checksum.yaml | 102 ++++++++++++------ taskfiles/checksum/tests.yaml | 150 ++++++++++++++++++++++++++ taskfiles/tests.yaml | 4 +- 3 files changed, 222 insertions(+), 34 deletions(-) create mode 100644 taskfiles/checksum/tests.yaml diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index e9b5ec7..bdbfa80 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -1,20 +1,27 @@ version: "3" -set: ["u", "pipefail"] -shopt: ["globstar"] - tasks: + # Compute the checksum of the given path include patterns saving the result to `CHECKSUM_FILE`. A + # calling task can set `FAIL` to false if they wish to continue after errors. + # # @param {string} CHECKSUM_FILE # @param {string[]} INCLUDE_PATTERNS Path wildcard patterns to compute the checksum for. # @param {string[]} [EXCLUDE_PATTERNS] Path wildcard patterns, relative to any `INCLUDE_PATTERNS`, # to exclude from the checksum. + # @param {string} [FAIL="true"] If set to "false" the task will not fail. compute: - desc: "Tries to compute a checksum for the given paths and output it to a file." internal: true label: "{{.TASK}}-{{.CHECKSUM_FILE}}" silent: true + vars: + FAIL: "{{if eq \"false\" .FAIL}}false{{else}}true{{end}}" + TAR: "{{if eq OS \"darwin\"}}gtar{{else}}tar{{end}}" + ERR_LOG: + sh: mktemp requires: - vars: ["CHECKSUM_FILE", "INCLUDE_PATTERNS"] + vars: + - "CHECKSUM_FILE" + - "INCLUDE_PATTERNS" cmds: # We explicitly set `--no-anchored` and `--wildcards` to make the inclusion behaviour match # the default exclusion behaviour. @@ -24,42 +31,60 @@ tasks: # input patterns cannot be quoted since they're evaluated by the shell and the results are # passed to `tar` as arguments. If the input patterns are passed to `tar` with quotes, the # pattern won't be evaluated and will instead be treated literally. - - >- - tar - --create - --file - - --group 0 - --mtime "UTC 1970-01-01" - --numeric-owner - --owner 0 - --sort name - --no-anchored - --wildcards - {{- range .EXCLUDE_PATTERNS}} - --exclude="{{.}}" - {{- end}} - {{- range .INCLUDE_PATTERNS}} - {{.}} - {{- end}} - 2> /dev/null - | md5sum > {{.CHECKSUM_FILE}} - # Ignore errors so that dependent tasks don't fail - ignore_error: true + - defer: "rm '{{.ERR_LOG}}'" + - |- + if ! \ + {{.TAR}} \ + --create \ + --file - \ + --group 0 \ + --mtime "UTC 1970-01-01" \ + --numeric-owner \ + --owner 0 \ + --sort name \ + --no-anchored \ + --wildcards \ + {{- range .EXCLUDE_PATTERNS}} + --exclude="{{.}}" \ + {{- end}} + {{- range .INCLUDE_PATTERNS}} + {{.}} \ + {{- end}} + 2> {{.ERR_LOG}} \ + | md5sum > {{.CHECKSUM_FILE}} \ + ; then + {{- if eq "true" .FAIL}} + echo "[{{.TASK}} error] tar failed with:\n$(cat {{.ERR_LOG}})" + exit 1 + {{- else}} + exit 0 + {{- end}} + fi + # Validates the checksum of the given path include patterns matches the checksum in the given + # file. If validation fails the checksum file is deleted so that a calling task with the checksum + # file in their `generates` field will be reran. A calling task can set `FAIL` to true if they + # wish to halt on failure. + # # @param {string} CHECKSUM_FILE # @param {string[]} INCLUDE_PATTERNS Path wildcard patterns to validate the checksum for. # @param {string[]} [EXCLUDE_PATTERNS] Path wildcard patterns, relative to any `INCLUDE_PATTERNS`, # to exclude from the checksum. + # @param {string} [FAIL="false"] If set to "true" the task fails. validate: - desc: "Validates the checksum of the given directory matches the checksum in the given file, or - deletes the checksum file otherwise." internal: true label: "{{.TASK}}-{{.CHECKSUM_FILE}}" silent: true vars: + FAIL: "{{if eq \"true\" .FAIL}}true{{else}}false{{end}}" TMP_CHECKSUM_FILE: "{{.CHECKSUM_FILE}}.tmp" + + ERR_LOG: + sh: mktemp requires: - vars: ["CHECKSUM_FILE", "INCLUDE_PATTERNS"] + vars: + - "CHECKSUM_FILE" + - "INCLUDE_PATTERNS" cmds: - task: "compute" vars: @@ -68,14 +93,25 @@ tasks: EXCLUDE_PATTERNS: ref: "default (list) .EXCLUDE_PATTERNS" CHECKSUM_FILE: "{{.TMP_CHECKSUM_FILE}}" + FAIL: "false" - defer: "rm -f '{{.TMP_CHECKSUM_FILE}}'" - # Check that all paths exist and the checksum matches; otherwise delete the checksum file. - |- ( + {{- $err_log := .ERR_LOG}} {{- range .INCLUDE_PATTERNS}} for path in {{.}}; do - test -e "$path" + test -e "$path" || (echo "Include path does not exist: $path" > $err_log; exit 1) done {{- end}} - diff -q "{{.TMP_CHECKSUM_FILE}}" "{{.CHECKSUM_FILE}}" 2> /dev/null - ) || rm -f "{{.CHECKSUM_FILE}}" + cmp -s "{{.TMP_CHECKSUM_FILE}}" "{{.CHECKSUM_FILE}}" \ + || ( + echo "cmp failed for '{{.TMP_CHECKSUM_FILE}}' '{{.CHECKSUM_FILE}}'" > "{{.ERR_LOG}}" + exit 1 + ) + ) || \ + {{- if eq "true" .FAIL}} + echo "[{{.TASK}} error] failed with:\n$(cat {{.ERR_LOG}})" + exit 1 + {{- else}} + rm -f "{{.CHECKSUM_FILE}}" + {{- end}} diff --git a/taskfiles/checksum/tests.yaml b/taskfiles/checksum/tests.yaml new file mode 100644 index 0000000..8242cd4 --- /dev/null +++ b/taskfiles/checksum/tests.yaml @@ -0,0 +1,150 @@ +version: "3" + +includes: + checksum: + internal: true + taskfile: "../../exports/taskfiles/utils/checksum.yaml" + +tasks: + default: + cmds: + - task: "checksum-test-rerun" + - task: "checksum-test-skip" + - task: "checksum-test-update" + + checksum-test-rerun: + vars: + OUTPUT_DIR: "{{.G_OUTPUT_DIR}}/{{.TASK | replace \":\" \"#\"}}" + SRC_DIR: "{{.OUTPUT_DIR}}/src" + + CHECKSUM_FILE: "{{.SRC_DIR}}.md5" + CHECKSUM_FILE_REF: "{{.CHECKSUM_FILE}}.ref" + FILE_0: "{{.SRC_DIR}}/0.txt" + FILE_1: "{{.SRC_DIR}}/1.txt" + cmds: + - task: "checksum-test-init" + vars: + OUTPUT_DIR: "{{.OUTPUT_DIR}}" + - task: "create-dir-with-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + FILE_PATH: "{{.FILE_0}}" + - "mv '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF}}'" + - task: "create-dir-with-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + FILE_PATH: "{{.FILE_1}}" + + # Test create-dir-with-checksum ran the second time and created a different checksum. + - "test ! -e '{{.FILE_0}}'" + - "test -e '{{.FILE_1}}'" + - |- + if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF}}'; then + exit 0 + else + exit 1 + fi + + checksum-test-skip: + vars: + OUTPUT_DIR: "{{.G_OUTPUT_DIR}}/{{.TASK | replace \":\" \"#\"}}" + SRC_DIR: "{{.OUTPUT_DIR}}/src" + + CHECKSUM_FILE: "{{.SRC_DIR}}.md5" + CHECKSUM_MOD_TS: "{{.CHECKSUM_FILE}}-mod-ts.txt" + FILE_0: "{{.SRC_DIR}}/0.txt" + FILE_1: "{{.SRC_DIR}}/1.txt" + cmds: + - task: "checksum-test-init" + vars: + OUTPUT_DIR: "{{.OUTPUT_DIR}}" + - task: "create-dir-with-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + FILE_PATH: "{{.FILE_0}}" + - "date -r '{{.CHECKSUM_FILE}}' > '{{.CHECKSUM_MOD_TS}}'" + - task: "create-dir-with-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + FILE_PATH: "{{.FILE_1}}" + + # Test create-dir-with-checksum didn't run the second time and the checksum is unmodified. + - "test -e '{{.FILE_0}}'" + - "test ! -e '{{.FILE_1}}'" + - "cmp -s '{{.CHECKSUM_MOD_TS}}' <(date -r '{{.CHECKSUM_FILE}}')" + + checksum-test-update: + vars: + OUTPUT_DIR: "{{.G_OUTPUT_DIR}}/{{.TASK | replace \":\" \"#\"}}" + SRC_DIR: "{{.OUTPUT_DIR}}/src" + + CHECKSUM_FILE: "{{.SRC_DIR}}.md5" + CHECKSUM_FILE_REF0: "{{.CHECKSUM_FILE}}.ref0" + CHECKSUM_FILE_REF1: "{{.CHECKSUM_FILE}}.ref1" + FILE_0: "{{.SRC_DIR}}/0.txt" + FILE_1: "{{.SRC_DIR}}/1.txt" + cmds: + - task: "checksum-test-init" + vars: + OUTPUT_DIR: "{{.OUTPUT_DIR}}" + - task: "create-dir-with-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + FILE_PATH: "{{.FILE_0}}" + - "cp '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'" + + - "cat '{{.CHECKSUM_FILE}}' > '{{.FILE_0}}'" + - task: "checksum:compute" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + INCLUDE_PATTERNS: ["{{.SRC_DIR}}"] + - "cp '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'" + + - task: "create-dir-with-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + FILE_PATH: "{{.FILE_1}}" + + # Test create-dir-with-checksum didn't run the second time and the updated checksum is + # different from the original. + - "test -e '{{.FILE_0}}'" + - "test ! -e '{{.FILE_1}}'" + - "cmp -s '{{.FILE_0}}' '{{.CHECKSUM_FILE_REF0}}'" + - "cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'" + - |- + if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'; then + exit 0 + else + exit 1 + fi + + checksum-test-init: + internal: true + requires: + vars: ["OUTPUT_DIR"] + cmds: + - "rm -rf '{{.OUTPUT_DIR}}'" + - "mkdir -p '{{.OUTPUT_DIR}}'" + + create-dir-with-checksum: + internal: true + vars: + DIR: "{{dir .FILE_PATH}}" + requires: + vars: ["CHECKSUM_FILE", "FILE_PATH"] + sources: ["{{.TASKFILE}}"] + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - task: "checksum:validate" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + INCLUDE_PATTERNS: ["{{.DIR}}"] + cmds: + - |- + rm -rf "{{.DIR}}" + mkdir -p "{{.DIR}}" + touch "{{.FILE_PATH}}" + - task: "checksum:compute" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + INCLUDE_PATTERNS: ["{{.DIR}}"] diff --git a/taskfiles/tests.yaml b/taskfiles/tests.yaml index 7c99a64..3d2efaf 100644 --- a/taskfiles/tests.yaml +++ b/taskfiles/tests.yaml @@ -2,6 +2,7 @@ version: "3" includes: boost: "boost/tests.yaml" + checksum: "checksum/tests.yaml" remote: "remote/tests.yaml" ystdlib-py: "ystdlib-py/tests.yaml" @@ -9,7 +10,8 @@ tasks: all: internal: true cmds: - - task: "boost" + # - task: "boost" + - task: "checksum" - task: "remote" - task: "ystdlib-py" From 394d45fba4dcc7666e51a2ad33e2dc6e0a576d8f Mon Sep 17 00:00:00 2001 From: davidlion Date: Mon, 25 Aug 2025 22:26:41 -0400 Subject: [PATCH 02/19] Fix yaml lint; sanity check --- exports/taskfiles/utils/checksum.yaml | 4 ++-- taskfiles/checksum/tests.yaml | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index bdbfa80..0d69ad9 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -17,7 +17,7 @@ tasks: FAIL: "{{if eq \"false\" .FAIL}}false{{else}}true{{end}}" TAR: "{{if eq OS \"darwin\"}}gtar{{else}}tar{{end}}" ERR_LOG: - sh: mktemp + sh: "mktemp" requires: vars: - "CHECKSUM_FILE" @@ -80,7 +80,7 @@ tasks: TMP_CHECKSUM_FILE: "{{.CHECKSUM_FILE}}.tmp" ERR_LOG: - sh: mktemp + sh: "mktemp" requires: vars: - "CHECKSUM_FILE" diff --git a/taskfiles/checksum/tests.yaml b/taskfiles/checksum/tests.yaml index 8242cd4..d5d90be 100644 --- a/taskfiles/checksum/tests.yaml +++ b/taskfiles/checksum/tests.yaml @@ -112,9 +112,7 @@ tasks: - "cmp -s '{{.FILE_0}}' '{{.CHECKSUM_FILE_REF0}}'" - "cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'" - |- - if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'; then - exit 0 - else + if cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'; then exit 1 fi From 3b5a816b218e6cf685a7ede7f1996644097bb76c Mon Sep 17 00:00:00 2001 From: davidlion Date: Mon, 25 Aug 2025 22:29:53 -0400 Subject: [PATCH 03/19] one more sanity check --- taskfiles/checksum/tests.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/taskfiles/checksum/tests.yaml b/taskfiles/checksum/tests.yaml index d5d90be..f35813f 100644 --- a/taskfiles/checksum/tests.yaml +++ b/taskfiles/checksum/tests.yaml @@ -112,9 +112,15 @@ tasks: - "cmp -s '{{.FILE_0}}' '{{.CHECKSUM_FILE_REF0}}'" - "cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'" - |- - if cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'; then + if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'; then + exit 0 + else exit 1 fi + - |- + if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'; then + exit 1 + fi checksum-test-init: internal: true From 9c935f9b40f3af30543643ec6bdd3c210299cf19 Mon Sep 17 00:00:00 2001 From: davidlion Date: Mon, 25 Aug 2025 22:34:29 -0400 Subject: [PATCH 04/19] one more --- taskfiles/checksum/tests.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/taskfiles/checksum/tests.yaml b/taskfiles/checksum/tests.yaml index f35813f..d50fd8f 100644 --- a/taskfiles/checksum/tests.yaml +++ b/taskfiles/checksum/tests.yaml @@ -112,13 +112,11 @@ tasks: - "cmp -s '{{.FILE_0}}' '{{.CHECKSUM_FILE_REF0}}'" - "cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'" - |- - if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'; then - exit 0 - else + if (cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'); then exit 1 fi - |- - if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'; then + if (! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'); then exit 1 fi From 07b88fc11db41505821f5b7c3491ab96a774ef41 Mon Sep 17 00:00:00 2001 From: davidlion Date: Mon, 25 Aug 2025 22:43:31 -0400 Subject: [PATCH 05/19] last one --- taskfiles/checksum/tests.yaml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/taskfiles/checksum/tests.yaml b/taskfiles/checksum/tests.yaml index d50fd8f..041fa69 100644 --- a/taskfiles/checksum/tests.yaml +++ b/taskfiles/checksum/tests.yaml @@ -41,9 +41,8 @@ tasks: - |- if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF}}'; then exit 0 - else - exit 1 fi + exit 1 checksum-test-skip: vars: @@ -112,13 +111,15 @@ tasks: - "cmp -s '{{.FILE_0}}' '{{.CHECKSUM_FILE_REF0}}'" - "cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'" - |- - if (cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'); then - exit 1 + if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF0}}'; then + exit 0 fi + exit 1 + # test - |- - if (! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'); then - exit 1 - fi + if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'; then + exit 1 + fi checksum-test-init: internal: true From 20802e784739100bb0fcb449b765415ccc4369d9 Mon Sep 17 00:00:00 2001 From: davidlion Date: Mon, 25 Aug 2025 22:46:05 -0400 Subject: [PATCH 06/19] remove test --- taskfiles/checksum/tests.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/taskfiles/checksum/tests.yaml b/taskfiles/checksum/tests.yaml index 041fa69..6986408 100644 --- a/taskfiles/checksum/tests.yaml +++ b/taskfiles/checksum/tests.yaml @@ -115,11 +115,6 @@ tasks: exit 0 fi exit 1 - # test - - |- - if ! cmp -s '{{.CHECKSUM_FILE}}' '{{.CHECKSUM_FILE_REF1}}'; then - exit 1 - fi checksum-test-init: internal: true From a2ff6f40c41e3e61ebef5bfdb84fe874cc1d3772 Mon Sep 17 00:00:00 2001 From: davidlion Date: Mon, 25 Aug 2025 23:12:09 -0400 Subject: [PATCH 07/19] Fix err log. --- exports/taskfiles/utils/checksum.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index 0d69ad9..61d9f38 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -94,13 +94,15 @@ tasks: ref: "default (list) .EXCLUDE_PATTERNS" CHECKSUM_FILE: "{{.TMP_CHECKSUM_FILE}}" FAIL: "false" - - defer: "rm -f '{{.TMP_CHECKSUM_FILE}}'" + - defer: |- + rm -f "{{.TMP_CHECKSUM_FILE}}" + rm "{{.ERR_LOG}}" - |- ( {{- $err_log := .ERR_LOG}} {{- range .INCLUDE_PATTERNS}} for path in {{.}}; do - test -e "$path" || (echo "Include path does not exist: $path" > $err_log; exit 1) + test -e "$path" || (echo "Include path does not exist: $path" > "{{$err_log}}"; exit 1) done {{- end}} cmp -s "{{.TMP_CHECKSUM_FILE}}" "{{.CHECKSUM_FILE}}" \ From 194440e5e26ffb80067e27d47c5603eebe858d78 Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 01:37:43 -0400 Subject: [PATCH 08/19] Drop mktemp due to task bug. --- exports/taskfiles/utils/checksum.yaml | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index 61d9f38..795aca6 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -16,8 +16,7 @@ tasks: vars: FAIL: "{{if eq \"false\" .FAIL}}false{{else}}true{{end}}" TAR: "{{if eq OS \"darwin\"}}gtar{{else}}tar{{end}}" - ERR_LOG: - sh: "mktemp" + TMP_ERR_LOG: "{{.CHECKSUM_FILE}}.log.tmp" requires: vars: - "CHECKSUM_FILE" @@ -31,7 +30,7 @@ tasks: # input patterns cannot be quoted since they're evaluated by the shell and the results are # passed to `tar` as arguments. If the input patterns are passed to `tar` with quotes, the # pattern won't be evaluated and will instead be treated literally. - - defer: "rm '{{.ERR_LOG}}'" + - defer: "rm '{{.TMP_ERR_LOG}}'" - |- if ! \ {{.TAR}} \ @@ -50,11 +49,11 @@ tasks: {{- range .INCLUDE_PATTERNS}} {{.}} \ {{- end}} - 2> {{.ERR_LOG}} \ + 2> {{.TMP_ERR_LOG}} \ | md5sum > {{.CHECKSUM_FILE}} \ ; then {{- if eq "true" .FAIL}} - echo "[{{.TASK}} error] tar failed with:\n$(cat {{.ERR_LOG}})" + echo "[{{.TASK}} error] tar failed with:\n$(cat {{.TMP_ERR_LOG}})" exit 1 {{- else}} exit 0 @@ -78,9 +77,7 @@ tasks: vars: FAIL: "{{if eq \"true\" .FAIL}}true{{else}}false{{end}}" TMP_CHECKSUM_FILE: "{{.CHECKSUM_FILE}}.tmp" - - ERR_LOG: - sh: "mktemp" + TMP_ERR_LOG: "{{.CHECKSUM_FILE}}.log.tmp" requires: vars: - "CHECKSUM_FILE" @@ -95,24 +92,25 @@ tasks: CHECKSUM_FILE: "{{.TMP_CHECKSUM_FILE}}" FAIL: "false" - defer: |- - rm -f "{{.TMP_CHECKSUM_FILE}}" - rm "{{.ERR_LOG}}" + rm "{{.TMP_CHECKSUM_FILE}}" + rm "{{.TMP_ERR_LOG}}" - |- ( - {{- $err_log := .ERR_LOG}} {{- range .INCLUDE_PATTERNS}} for path in {{.}}; do - test -e "$path" || (echo "Include path does not exist: $path" > "{{$err_log}}"; exit 1) + test -e "$path" \ + || (echo "Include path does not exist: $path" > "{{$.TMP_ERR_LOG}}"; exit 1) done {{- end}} cmp -s "{{.TMP_CHECKSUM_FILE}}" "{{.CHECKSUM_FILE}}" \ || ( - echo "cmp failed for '{{.TMP_CHECKSUM_FILE}}' '{{.CHECKSUM_FILE}}'" > "{{.ERR_LOG}}" + echo "cmp failed for '{{.TMP_CHECKSUM_FILE}}' '{{.CHECKSUM_FILE}}'" \ + > "{{.TMP_ERR_LOG}}" exit 1 ) ) || \ {{- if eq "true" .FAIL}} - echo "[{{.TASK}} error] failed with:\n$(cat {{.ERR_LOG}})" + echo "[{{.TASK}} error] failed with:\n$(cat {{.TMP_ERR_LOG}})" exit 1 {{- else}} rm -f "{{.CHECKSUM_FILE}}" From 3a92d534e8a032a7d6967fb8688c481869971da0 Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 01:41:25 -0400 Subject: [PATCH 09/19] Add force to rm tmp files. --- exports/taskfiles/utils/checksum.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index 795aca6..4b12abd 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -30,7 +30,7 @@ tasks: # input patterns cannot be quoted since they're evaluated by the shell and the results are # passed to `tar` as arguments. If the input patterns are passed to `tar` with quotes, the # pattern won't be evaluated and will instead be treated literally. - - defer: "rm '{{.TMP_ERR_LOG}}'" + - defer: "rm -f '{{.TMP_ERR_LOG}}'" - |- if ! \ {{.TAR}} \ @@ -92,8 +92,8 @@ tasks: CHECKSUM_FILE: "{{.TMP_CHECKSUM_FILE}}" FAIL: "false" - defer: |- - rm "{{.TMP_CHECKSUM_FILE}}" - rm "{{.TMP_ERR_LOG}}" + rm -f "{{.TMP_CHECKSUM_FILE}}" + rm -f "{{.TMP_ERR_LOG}}" - |- ( {{- range .INCLUDE_PATTERNS}} From c31ef2428792ef959e61c0b35bacc8d752a58071 Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 02:15:23 -0400 Subject: [PATCH 10/19] sub shells --- exports/taskfiles/utils/checksum.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index 4b12abd..74de95c 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -108,10 +108,11 @@ tasks: > "{{.TMP_ERR_LOG}}" exit 1 ) - ) || \ + ) || ( \ {{- if eq "true" .FAIL}} echo "[{{.TASK}} error] failed with:\n$(cat {{.TMP_ERR_LOG}})" exit 1 {{- else}} rm -f "{{.CHECKSUM_FILE}}" {{- end}} + ) From 07fd3e4cbc7759424cff8421061caa1eb4142f81 Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 02:25:29 -0400 Subject: [PATCH 11/19] Update readme with macos requirements. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index a49d797..09613a3 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ Before you submit a pull request, ensure you follow the testing and linting inst * [Task] 3.40 or higher * [uv] 0.7.10 or higher +### MacOS + +The exported tasks use GNU utilities that are not always pre-installed on MacOS. You may need to +install the following brew packages: + +* [coreutils] +* [gnu-tar] + ## Testing To run all tests: @@ -42,5 +50,7 @@ To clean up any generated files: task clean ``` +[coreutils]: https://formulae.brew.sh/formula/coreutils +[gnu-tar]: https://formulae.brew.sh/formula/gnu-tar [Task]: https://taskfile.dev/ [uv]: https://docs.astral.sh/uv From 4ec658897d6dfffa7924c13d62c063ab8289171d Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 12:49:38 -0400 Subject: [PATCH 12/19] Tweak default list handling. --- exports/taskfiles/utils/checksum.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index 74de95c..ef1376f 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -14,7 +14,10 @@ tasks: label: "{{.TASK}}-{{.CHECKSUM_FILE}}" silent: true vars: + EXCLUDE_PATTERNS: + ref: "default (list) .EXCLUDE_PATTERNS" FAIL: "{{if eq \"false\" .FAIL}}false{{else}}true{{end}}" + TAR: "{{if eq OS \"darwin\"}}gtar{{else}}tar{{end}}" TMP_ERR_LOG: "{{.CHECKSUM_FILE}}.log.tmp" requires: @@ -85,11 +88,11 @@ tasks: cmds: - task: "compute" vars: + CHECKSUM_FILE: "{{.TMP_CHECKSUM_FILE}}" INCLUDE_PATTERNS: ref: ".INCLUDE_PATTERNS" EXCLUDE_PATTERNS: - ref: "default (list) .EXCLUDE_PATTERNS" - CHECKSUM_FILE: "{{.TMP_CHECKSUM_FILE}}" + ref: ".EXCLUDE_PATTERNS" FAIL: "false" - defer: |- rm -f "{{.TMP_CHECKSUM_FILE}}" From 93a6296679b12e4b169a2fb9308e87e611680db8 Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 12:52:44 -0400 Subject: [PATCH 13/19] readme tweak --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 09613a3..2d39429 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,13 @@ Before you submit a pull request, ensure you follow the testing and linting inst * [Task] 3.40 or higher * [uv] 0.7.10 or higher -### MacOS +### macOS -The exported tasks use GNU utilities that are not always pre-installed on MacOS. You may need to -install the following brew packages: +The exported tasks use GNU utilities that are not always pre-installed on macOS. You may need to +install the following brew packages and add their executables to your PATH: -* [coreutils] -* [gnu-tar] +* [coreutils]\: `md5sum` +* [gnu-tar]\: `gtar` ## Testing From 5a230b96fdf040364b96a748892e7ec8bca34067 Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 13:01:29 -0400 Subject: [PATCH 14/19] doc string tweaks --- exports/taskfiles/utils/checksum.yaml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index ef1376f..86199ee 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -1,8 +1,10 @@ version: "3" tasks: - # Compute the checksum of the given path include patterns saving the result to `CHECKSUM_FILE`. A - # calling task can set `FAIL` to false if they wish to continue after errors. + + # Compute the checksum of the given path include/exclude patterns, saving the result to + # `CHECKSUM_FILE`. The calling task can set `FAIL` to "false" if they wish to continue if checksum + # computation fails. # # @param {string} CHECKSUM_FILE # @param {string[]} INCLUDE_PATTERNS Path wildcard patterns to compute the checksum for. @@ -63,10 +65,10 @@ tasks: {{- end}} fi - # Validates the checksum of the given path include patterns matches the checksum in the given - # file. If validation fails the checksum file is deleted so that a calling task with the checksum - # file in their `generates` field will be reran. A calling task can set `FAIL` to true if they - # wish to halt on failure. + # Validates the checksum of the given path include/exclude patterns matches the checksum in the + # given file. If validation fails the checksum file is deleted, but the task succeeds so that the + # calling task will be reran (by using the checksum file in their `generates` field). The calling + # task can set `FAIL` to "true" if they wish for this task to fail if validation fails. # # @param {string} CHECKSUM_FILE # @param {string[]} INCLUDE_PATTERNS Path wildcard patterns to validate the checksum for. From 01280e1f8436855bf2222866b5cb49fb80c5349d Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 15:44:59 -0400 Subject: [PATCH 15/19] fix sub shell and indentation. --- exports/taskfiles/utils/checksum.yaml | 77 +++++++++++++++------------ 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index 86199ee..d649696 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -38,27 +38,28 @@ tasks: - defer: "rm -f '{{.TMP_ERR_LOG}}'" - |- if ! \ - {{.TAR}} \ - --create \ - --file - \ - --group 0 \ - --mtime "UTC 1970-01-01" \ - --numeric-owner \ - --owner 0 \ - --sort name \ - --no-anchored \ - --wildcards \ - {{- range .EXCLUDE_PATTERNS}} - --exclude="{{.}}" \ - {{- end}} - {{- range .INCLUDE_PATTERNS}} - {{.}} \ - {{- end}} - 2> {{.TMP_ERR_LOG}} \ - | md5sum > {{.CHECKSUM_FILE}} \ + {{.TAR}} \ + --create \ + --file - \ + --group 0 \ + --mtime "UTC 1970-01-01" \ + --numeric-owner \ + --owner 0 \ + --sort name \ + --no-anchored \ + --wildcards \ + {{- range .EXCLUDE_PATTERNS}} + --exclude="{{.}}" \ + {{- end}} + {{- range .INCLUDE_PATTERNS}} + {{.}} \ + {{- end}} + 2> "{{.TMP_ERR_LOG}}" \ + | md5sum > "{{.CHECKSUM_FILE}}" \ ; then + rm "{{.CHECKSUM_FILE}}" {{- if eq "true" .FAIL}} - echo "[{{.TASK}} error] tar failed with:\n$(cat {{.TMP_ERR_LOG}})" + printf "[{{.TASK}} error] failed with:\n%s\n" "$(cat {{.TMP_ERR_LOG}})" exit 1 {{- else}} exit 0 @@ -102,22 +103,28 @@ tasks: - |- ( {{- range .INCLUDE_PATTERNS}} - for path in {{.}}; do - test -e "$path" \ - || (echo "Include path does not exist: $path" > "{{$.TMP_ERR_LOG}}"; exit 1) - done + for path in {{.}}; do + test -e "${path}" \ + || ( + echo "Include path does not exist: ${path}" > "{{$.TMP_ERR_LOG}}" + exit 1 + ) + done && \ {{- end}} - cmp -s "{{.TMP_CHECKSUM_FILE}}" "{{.CHECKSUM_FILE}}" \ + ( + cmp -s "{{.TMP_CHECKSUM_FILE}}" "{{.CHECKSUM_FILE}}" \ + || ( + echo "cmp failed for '{{.TMP_CHECKSUM_FILE}}' '{{.CHECKSUM_FILE}}'" \ + > "{{.TMP_ERR_LOG}}" + exit 1 + ) + ) + ) \ || ( - echo "cmp failed for '{{.TMP_CHECKSUM_FILE}}' '{{.CHECKSUM_FILE}}'" \ - > "{{.TMP_ERR_LOG}}" - exit 1 + {{- if eq "true" .FAIL}} + printf "[{{.TASK}} error] failed with:\n%s\n" "$(cat {{.TMP_ERR_LOG}})" + exit 1 + {{- else}} + rm -f "{{.CHECKSUM_FILE}}" + {{- end}} ) - ) || ( \ - {{- if eq "true" .FAIL}} - echo "[{{.TASK}} error] failed with:\n$(cat {{.TMP_ERR_LOG}})" - exit 1 - {{- else}} - rm -f "{{.CHECKSUM_FILE}}" - {{- end}} - ) From b6df4171338b3a9a11ccf12d7632e54535fe4254 Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 26 Aug 2025 15:57:54 -0400 Subject: [PATCH 16/19] Add -f when removing checksum on failed compute. --- exports/taskfiles/utils/checksum.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index d649696..950f600 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -57,7 +57,7 @@ tasks: 2> "{{.TMP_ERR_LOG}}" \ | md5sum > "{{.CHECKSUM_FILE}}" \ ; then - rm "{{.CHECKSUM_FILE}}" + rm -f "{{.CHECKSUM_FILE}}" {{- if eq "true" .FAIL}} printf "[{{.TASK}} error] failed with:\n%s\n" "$(cat {{.TMP_ERR_LOG}})" exit 1 From fbe8a72c8e523180dc818c6fc145c5ffd2036438 Mon Sep 17 00:00:00 2001 From: davidlion Date: Mon, 22 Sep 2025 09:57:35 -0400 Subject: [PATCH 17/19] Undo testing comment. --- taskfiles/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/taskfiles/tests.yaml b/taskfiles/tests.yaml index 3d2efaf..84626f9 100644 --- a/taskfiles/tests.yaml +++ b/taskfiles/tests.yaml @@ -10,7 +10,7 @@ tasks: all: internal: true cmds: - # - task: "boost" + - task: "boost" - task: "checksum" - task: "remote" - task: "ystdlib-py" From 8534c248e86584733e770ff1e4e231aae4884386 Mon Sep 17 00:00:00 2001 From: davidlion Date: Wed, 15 Oct 2025 10:05:54 -0400 Subject: [PATCH 18/19] Apply suggestions from code review. Co-authored-by: Bingran Hu --- exports/taskfiles/utils/checksum.yaml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/exports/taskfiles/utils/checksum.yaml b/exports/taskfiles/utils/checksum.yaml index 950f600..ebcb571 100644 --- a/exports/taskfiles/utils/checksum.yaml +++ b/exports/taskfiles/utils/checksum.yaml @@ -20,7 +20,7 @@ tasks: ref: "default (list) .EXCLUDE_PATTERNS" FAIL: "{{if eq \"false\" .FAIL}}false{{else}}true{{end}}" - TAR: "{{if eq OS \"darwin\"}}gtar{{else}}tar{{end}}" + ARCHIVER: "{{if eq OS \"darwin\"}}gtar{{else}}tar{{end}}" TMP_ERR_LOG: "{{.CHECKSUM_FILE}}.log.tmp" requires: vars: @@ -38,7 +38,7 @@ tasks: - defer: "rm -f '{{.TMP_ERR_LOG}}'" - |- if ! \ - {{.TAR}} \ + {{.ARCHIVER}} \ --create \ --file - \ --group 0 \ @@ -66,16 +66,17 @@ tasks: {{- end}} fi - # Validates the checksum of the given path include/exclude patterns matches the checksum in the - # given file. If validation fails the checksum file is deleted, but the task succeeds so that the - # calling task will be reran (by using the checksum file in their `generates` field). The calling - # task can set `FAIL` to "true" if they wish for this task to fail if validation fails. + # Validates that the checksum computed from the given include/exclude path patterns matches the + # reference checksum stored in the given file. If validation fails, the checksum file is deleted, + # but the task succeeds so dependent tasks that list the checksum file under `generates` will + # rerun automatically. The calling task can set `FAIL` to "true" to make this task fail when the + # validation is expected to succeed. # # @param {string} CHECKSUM_FILE # @param {string[]} INCLUDE_PATTERNS Path wildcard patterns to validate the checksum for. # @param {string[]} [EXCLUDE_PATTERNS] Path wildcard patterns, relative to any `INCLUDE_PATTERNS`, # to exclude from the checksum. - # @param {string} [FAIL="false"] If set to "true" the task fails. + # @param {string} [FAIL="false"] If set to "true", the task fails when checksums mismatch. validate: internal: true label: "{{.TASK}}-{{.CHECKSUM_FILE}}" From bbb32bc21b900bff45e5fbb755f9e53401ed9052 Mon Sep 17 00:00:00 2001 From: davidlion Date: Tue, 28 Oct 2025 01:00:08 -0400 Subject: [PATCH 19/19] Add individual checksum task to tests. --- taskfiles/tests.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/taskfiles/tests.yaml b/taskfiles/tests.yaml index 84626f9..a303a09 100644 --- a/taskfiles/tests.yaml +++ b/taskfiles/tests.yaml @@ -19,6 +19,10 @@ tasks: cmds: - task: "boost:test" + checksum: + cmds: + - task: "checksum:default" + remote: cmds: - task: "remote:default"