diff --git a/taskfiles/utils.yml b/taskfiles/utils.yml index 13bdded..2309bee 100644 --- a/taskfiles/utils.yml +++ b/taskfiles/utils.yml @@ -1,64 +1,77 @@ version: "3" +set: ["u", "pipefail"] +shopt: ["globstar"] + tasks: # === # CHECKSUM UTILS # === - # @param {string} DATA_DIR The directory to compute the checksum for. + # @param {string[]} DATA_PATTERNS Path wildcard patterns to compute the checksum for. # @param {string} OUTPUT_FILE - # @param {[]string} [EXCLUDE_PATHS] A list of paths, relative to `DATA_DIR`, to exclude from the - # checksum. + # @param {string[]} [EXCLUDE_PATTERNS] Path wildcard patterns, relative to any `DATA_PATTERNS`, to + # exclude from the checksum. compute-checksum: - desc: "Tries to compute a checksum for the given directory and output it to a file." + desc: "Tries to compute a checksum for the given paths and output it to a file." internal: true + label: "{{.TASK}}-{{.OUTPUT_FILE}}" silent: true requires: - vars: ["DATA_DIR", "OUTPUT_FILE"] + vars: ["DATA_PATTERNS", "OUTPUT_FILE"] cmds: - >- tar cf - - --directory "{{.DATA_DIR}}" --group 0 --mtime "UTC 1970-01-01" --numeric-owner --owner 0 --sort name - {{- range .EXCLUDE_PATHS}} + --no-anchored + --wildcards + {{- range .EXCLUDE_PATTERNS}} --exclude="{{.}}" {{- end}} - . 2> /dev/null + {{- range .DATA_PATTERNS}} + {{.}} + {{- end}} + 2> /dev/null | md5sum > {{.OUTPUT_FILE}} # Ignore errors so that dependent tasks don't fail ignore_error: true - # @param {string} DATA_DIR The directory to validate the checksum for. + # @param {string[]} DATA_PATTERNS Path wildcard patterns to validate the checksum for. # @param {string} OUTPUT_FILE - # @param {[]string} [EXCLUDE_PATHS] A list of paths, relative to `DATA_DIR`, to exclude from the - # checksum. + # @param {string[]} [EXCLUDE_PATTERNS] Path wildcard patterns, relative to any `DATA_PATTERNS`, to + # exclude from the checksum. validate-checksum: 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: TMP_CHECKSUM_FILE: "{{.CHECKSUM_FILE}}.tmp" requires: - vars: ["CHECKSUM_FILE", "DATA_DIR"] + vars: ["CHECKSUM_FILE", "DATA_PATTERNS"] cmds: - task: "compute-checksum" vars: - DATA_DIR: "{{.DATA_DIR}}" - EXCLUDE_PATHS: - ref: "default (list) .EXCLUDE_PATHS" + DATA_PATTERNS: + ref: ".DATA_PATTERNS" + EXCLUDE_PATTERNS: + ref: "default (list) .EXCLUDE_PATTERNS" OUTPUT_FILE: "{{.TMP_CHECKSUM_FILE}}" - defer: "rm -f '{{.TMP_CHECKSUM_FILE}}'" - # Check that the directory exists and the checksum matches; otherwise delete the checksum file - - >- - ( - test -d "{{.DATA_DIR}}" - && diff -q '{{.TMP_CHECKSUM_FILE}}' '{{.CHECKSUM_FILE}}' 2> /dev/null - ) || rm -f '{{.CHECKSUM_FILE}}' + # Check that all paths exist and the checksum matches; otherwise delete the checksum file. + - |- + ( {{- range .DATA_PATTERNS}} + for path in {{.}}; do + test -e "$path" + done + {{- end}} + diff -q "{{.TMP_CHECKSUM_FILE}}" "{{.CHECKSUM_FILE}}" 2> /dev/null + ) || rm -f "{{.CHECKSUM_FILE}}" # === # STRING UTILS @@ -75,7 +88,7 @@ tasks: # 2. We can't use `--regexp` instead of `-E` since `--regexp` is not supported on macOS src="{{.FILE_PATH}}" dst="{{.FILE_PATH}}.tmp" - sed -E '{{.SED_EXP}}' "${src}" > "${dst}" + sed -E "{{.SED_EXP}}" "${src}" > "${dst}" mv "${dst}" "${src}" # === @@ -111,7 +124,7 @@ tasks: # Runs clang-format on C++ files at the given paths. # # @param {string} FLAGS Any flags to pass to clang-format. - # @param {[]string} SRC_PATHS The paths on which to run clang-format. + # @param {string[]} SRC_PATHS The paths on which to run clang-format. # @param {string} VENV_DIR Python virtual environment where clang-format is installed. clang-format: internal: true @@ -128,7 +141,7 @@ tasks: # Runs clang-tidy on C++ files at the given paths. # # @param {string} FLAGS Any flags to pass to clang-tidy. - # @param {[]string} SRC_PATHS The paths on which to run clang-tidy. + # @param {string[]} SRC_PATHS The paths on which to run clang-tidy. # @param {string} VENV_DIR Python virtual environment where clang-tidy is installed. clang-tidy: internal: true @@ -141,3 +154,158 @@ tasks: \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.hpp" \) \ -print0 | \ xargs -0 clang-tidy {{.FLAGS}} + + # === + # CMAKE UTILS + # === + + # Runs CMake's configure and build steps for the given source and build directories. + # + # @param {string} BUILD_DIR CMake build directory to create. + # @param {string} SOURCE_DIR Project source directory containing the CMakeLists.txt file. + # @param {string=""} [CMAKE_ARGS] Any additional arguments to pass to CMake's configure step. + cmake-config-and-build: + internal: true + label: "{{.TASK}}-{{.SOURCE_DIR}}-{{.BUILD_DIR}}" + vars: + CMAKE_ARGS: >- + {{default "" .CMAKE_ARGS}} + requires: + vars: ["BUILD_DIR", "SOURCE_DIR"] + cmds: + - >- + cmake + -S "{{.SOURCE_DIR}}" + -B "{{.BUILD_DIR}}" + {{.CMAKE_ARGS}} + - >- + cmake + --build "{{.BUILD_DIR}}" + --parallel + + # Runs the CMake install step for the given build directory. + # + # @param {string} BUILD_DIR CMake build directory. + # @param {string} INSTALL_PREFIX Path prefix of where the project should be installed. + cmake-install: + internal: true + label: "{{.TASK}}-{{.BUILD_DIR}}-{{.INSTALL_PREFIX}}" + requires: + vars: ["BUILD_DIR", "INSTALL_PREFIX"] + cmds: + - >- + cmake + --install "{{.BUILD_DIR}}" + --prefix "{{.INSTALL_PREFIX}}" + + # === + # REMOTE UTILS + # === + + # Runs curl to download a file from the given URL. + # + # @param {string} URL + # @param {string} FILE_SHA256 Content hash to verify the downloaded file against. + # @param {string={{(base .URL)}}} [OUTPUT_FILE] Path where the file should be stored. + curl: + internal: true + label: "{{.TASK}}-{{.OUTPUT_FILE}}" + vars: + OUTPUT_FILE: "{{default (base .URL) .OUTPUT_FILE}}" + requires: + vars: ["FILE_SHA256", "URL"] + generates: ["{{.OUTPUT_FILE}}"] + status: + - >- + diff + <(echo "{{.FILE_SHA256}}") + <(openssl dgst -sha256 "{{.OUTPUT_FILE}}" + | awk '{print $2}') + cmds: + - |- + mkdir -p "{{dir .OUTPUT_FILE}}" + max_attempts=3 + attempt=1 + while [ $attempt -le $max_attempts ]; do + if curl \ + --fail \ + --location \ + --show-error \ + --connect-timeout 10 \ + --max-time 300 \ + "{{.URL}}" \ + --output "{{.OUTPUT_FILE}}"; + then + break + fi + attempt=$((attempt + 1)) + sleep 5 + done + if [ $attempt -gt $max_attempts ]; then + echo "Failed to download after $max_attempts attempts." + exit 1 + fi + + # Runs curl to download the tar file from the given URL and extracts its contents. + # + # @param {string} OUTPUT_DIR Directory in which to extract the tar file. + # @param {string} URL + # @param {string} FILE_SHA256 Content hash to verify the downloaded tar file against. + # @param {string={{.OUTPUT_DIR}}.md5} [CHECKSUM_FILE] File path to store the checksum of + # downloaded tar file. + # @param {string[]=[]} [EXCLUDE_PATTERNS] Path wildcard patterns that should not be extracted. + # @param {string[]=[]} [DATA_PATTERNS] Path wildcard patterns to extract. + # @param {int=1} [NUM_COMPONENTS_TO_STRIP] Number of leading path components to strip from the + # extracted files. + # @param {string={{.OUTPUT_DIR}}.tar.gz} [TAR_FILE] Path where the tar file should be stored. + download-and-extract-tar: + internal: true + label: "{{.TASK}}-{{.OUTPUT_DIR}}" + vars: + CHECKSUM_FILE: >- + {{default (printf "%s.md5" .OUTPUT_DIR) .CHECKSUM_FILE}} + DATA_PATTERNS: + ref: "default (list) .DATA_PATTERNS" + EXCLUDE_PATTERNS: + ref: "default (list) .EXCLUDE_PATTERNS" + NUM_COMPONENTS_TO_STRIP: "{{default 1 .NUM_COMPONENTS_TO_STRIP}}" + TAR_FILE: >- + {{default (printf "%s.tar.gz" .OUTPUT_DIR) .TAR_FILE}} + requires: + vars: ["FILE_SHA256", "OUTPUT_DIR", "URL"] + sources: ["{{.TASKFILE}}"] + generates: ["{{.CHECKSUM_FILE}}", "{{.TAR_FILE}}"] + deps: + - task: "curl" + vars: + FILE_SHA256: "{{.FILE_SHA256}}" + OUTPUT_FILE: "{{.TAR_FILE}}" + URL: "{{.URL}}" + - task: "validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_PATTERNS: ["{{.OUTPUT_DIR}}"] + cmds: + - |- + rm -rf "{{.OUTPUT_DIR}}" + mkdir -p "{{.OUTPUT_DIR}}" + - >- + tar + --extract + --strip-components="{{.NUM_COMPONENTS_TO_STRIP}}" + --directory "{{.OUTPUT_DIR}}" + --file "{{.TAR_FILE}}" + --no-anchored + --wildcards + {{- range .EXCLUDE_PATTERNS}} + --exclude="{{.}}" + {{- end}} + {{- range .DATA_PATTERNS}} + "{{.}}" + {{- end}} + 2> /dev/null + # This command must be last + - task: "compute-checksum" + vars: + DATA_PATTERNS: ["{{.OUTPUT_DIR}}"] + OUTPUT_FILE: "{{.CHECKSUM_FILE}}"