diff --git a/cmake/FindDkToolScripts.cmake b/cmake/FindDkToolScripts.cmake new file mode 100644 index 0000000..fb060e9 --- /dev/null +++ b/cmake/FindDkToolScripts.cmake @@ -0,0 +1,229 @@ +########################################################################## +# File: dktool/cmake/FindDkToolScripts.cmake # +# # +# Copyright 2023 Diskuv, Inc. # +# # +# Licensed under the Open Software License version 3.0 # +# (the "License"); you may not use this file except in compliance # +# with the License. You may obtain a copy of the License at # +# # +# https://opensource.org/license/osl-3-0-php/ # +# # +########################################################################## + +# Recommendation: Place this file in source control. +# Auto-generated by `./dk dksdk.project.new` of dktool. + +include(FetchContent) + +function(parse_dktool_command_line) + # The first argument is . All dots will be replaced with a + # triple underscore as a convenience and to be pretty for the user. + # However, we do not error if no is given ... we'll do + # that later. + set(command) + set(expected_function_name) + set(quotedArgs "") + if(ARGC EQUAL 0 OR (ARGC EQUAL 1 AND ARGV0 STREQUAL HELP)) + message(NOTICE [[Usage: + ./dk HELP + ./dk [args] +]]) + else() + set(command ${ARGV0}) + string(REPLACE "." "___" expected_function_name ${command}) + message(VERBOSE "Searching for ${expected_function_name}") + + # Parse the remainder of the arguments [args] + # * Use technique from [Professional CMake: A Practical Guide - Forwarding Command Arguments] + # to be able to forward arguments correctly to an inner function (the function). + cmake_parse_arguments(PARSE_ARGV 1 FWD "" "" "") + foreach(arg IN LISTS FWD_UNPARSED_ARGUMENTS) + string(APPEND quotedArgs " [===[${arg}]===]") + endforeach() + endif() + + # Set policies (we are in a new EVAL CODE context) + # Included scripts do automatic cmake_policy PUSH and POP + if(POLICY CMP0011) + cmake_policy(SET CMP0011 NEW) + endif() + # Allow GIT_SUBMODULES empty to mean no submodules + if(POLICY CMP0097) + cmake_policy(SET CMP0097 NEW) + endif() + + # Setup the binary directory + if(NOT DKTOOL_WORKDIR) + message(FATAL_ERROR "Illegal state. Expecting DKTOOL_WORKDIR") + endif() + set(CMAKE_BINARY_DIR ${DKTOOL_WORKDIR}) + set(CMAKE_CURRENT_BINARY_DIR ${CMAKE_BINARY_DIR}) + + # Search in all the user scripts + set(dot_function_names) + file(GLOB_RECURSE command_files + LIST_DIRECTORIES FALSE + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts + cmake/scripts/*.cmake) + foreach(command_file IN LISTS command_files) + # Normalize and lowercase + cmake_path(NORMAL_PATH command_file) + string(TOLOWER "${command_file}" command_file) + cmake_path(REMOVE_EXTENSION command_file OUTPUT_VARIABLE command_file_no_ext) + + # Convert to list + string(REPLACE "/" ";" command_stems_no_namespace ${command_file_no_ext}) + + # Make a pretty description only for validation + set(pretty_stems ${command_stems_no_namespace}) + list(TRANSFORM pretty_stems PREPEND "'") + list(TRANSFORM pretty_stems APPEND "'") + string(JOIN ", " pretty_stems_str ${pretty_stems}) + string(REGEX REPLACE ",([^,]*)" " and \\1" pretty_stems_str "${pretty_stems_str}") + + # Validate that only alphanumeric with underscores (but not the reserved three underscores) + string(REGEX MATCH "[/a-z0-9_]*" only_alphanum_and_underscores "${command_file_no_ext}") + if(NOT only_alphanum_and_underscores STREQUAL "${command_file_no_ext}") + message(WARNING "Ignoring user script ${CMAKE_CURRENT_SOURCE_DIR}/${command_file}. +The stems of the user script (${pretty_stems_str}) must only contain letters, numbers and underscores.") + continue() + endif() + string(FIND "${command_file_no_ext}" "___" reserved_underscores) + if(reserved_underscores GREATER_EQUAL 0) + message(WARNING "Ignoring user script ${CMAKE_CURRENT_SOURCE_DIR}/${command_file}. +No stem of the user script (${pretty_stems_str}) can contain a triple underscore ('___').") + continue() + endif() + + # Translate dev/xxx.cmake to the "user" namespaced function name + # `user__dev__xxx` and `user.dev.xxx`. + set(command_stems ${command_stems_no_namespace}) + list(PREPEND command_stems "user") + string(JOIN "___" command_function_name ${command_stems}) + string(JOIN "." dot_function_name ${command_stems}) + list(APPEND dot_function_names ${dot_function_name}) + + # In a new scope (to avoid a global, leaky namespace) register the function. + message(VERBOSE "Shimming ${command_function_name}") + cmake_language(EVAL CODE " +function(${command_function_name}) + include(\"${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/${command_file}\") + if(COMMAND run) + run(${quotedArgs}) + else() + message(FATAL_ERROR [[The user script ${CMAKE_CURRENT_SOURCE_DIR}/cmake/scripts/${command_file} was missing: + function(run) + # Your user code + endfunction() +]]) + endif() +endfunction() +") + endforeach() + + # Include all the system scripts. + # - Since the system scripts come after the user scripts, the user scripts + # don't override the system scripts unless the user scripts use deferred + # hooks or redefine CMake built-in functions. Regardless, the user + # scripts are namespaced with `user__` prefix + if(NOT IS_DIRECTORY cmake/scripts/dksdk) + # If this project (ex. dktool) has the system scripts, it must + # have all of them. Otherwise we download the system scripts. + FetchContent_Populate(dktool + QUIET + GIT_REPOSITORY https://gitlab.com/diskuv/dktool.git + GIT_TAG 1.0 + # As of 3.25.3 the bug https://gitlab.kitware.com/cmake/cmake/-/issues/24578 + # has still not been fixed. That means empty strings get removed. + # ExternalProject_Add(GIT_SUBMODULES) in dktool-subbuild/CMakeLists.txt + # means fetch all submodules. + # https://gitlab.kitware.com/cmake/cmake/-/issues/20579#note_734045 + # has a workaround. + GIT_SUBMODULES cmake # Non-git-submodule dir that already exists + GIT_SUBMODULES_RECURSE OFF + ) + file(GLOB_RECURSE system_command_files + LIST_DIRECTORIES FALSE + RELATIVE ${dktool_SOURCE_DIR}/cmake/scripts + ${dktool_SOURCE_DIR}/cmake/scripts/dkml/*.cmake + ${dktool_SOURCE_DIR}/cmake/scripts/dksdk/*.cmake) + foreach(command_file IN LISTS system_command_files) + # Normalize and lowercase + cmake_path(NORMAL_PATH command_file) + string(TOLOWER "${command_file}" command_file) + cmake_path(REMOVE_EXTENSION command_file OUTPUT_VARIABLE command_file_no_ext) + + # Convert to list + string(REPLACE "/" ";" command_stems_no_namespace ${command_file_no_ext}) + + # Translate dksdk/xxx.cmake to the function name `dksdk__xxx` and `dksdk.xxx` + set(command_stems ${command_stems_no_namespace}) + string(JOIN "___" command_function_name ${command_stems}) + string(JOIN "." dot_function_name ${command_stems}) + list(APPEND dot_function_names ${dot_function_name}) + + # In a new scope (to avoid a global, leaky namespace) register the function. + message(VERBOSE "Shimming ${command_function_name}") + cmake_language(EVAL CODE " +function(${command_function_name}) + include(\"${dktool_SOURCE_DIR}/cmake/scripts/${command_file}\") + if(COMMAND run) + run(${quotedArgs}) + else() + message(FATAL_ERROR [[The system script ${dktool_SOURCE_DIR}/cmake/scripts/${command_file} was missing: + function(run) + # The system code + endfunction() +]]) + endif() +endfunction() +") + + endforeach() + endif() + + # Pretty function names that are available + set(pretty_function_names ${dot_function_names}) + list(TRANSFORM pretty_function_names PREPEND " ") + list(TRANSFORM pretty_function_names APPEND "\n") + string(JOIN "" str_pretty_function_names ${pretty_function_names}) + + # Exit if no + if(NOT command) + message(NOTICE "The following commands are available: +${str_pretty_function_names}") + return() + endif() + + # Validate the exists + if(NOT COMMAND ${expected_function_name}) + message(FATAL_ERROR "No command '${command}' exists. The following commands are available: +${str_pretty_function_names}") + message(FATAL_ERROR "No command '${command}' exists") + endif() + + # Call the function + cmake_language(EVAL CODE "${expected_function_name}()") +endfunction() + +# DkML data home +if(WIN32) + set(DKML_DATA_HOME "$ENV{LOCALAPPDATA}/Programs/DiskuvOCaml") +elseif(DEFINED ENV{XDG_DATA_HOME}) + set(DKML_DATA_HOME "$ENV{XDG_DATA_HOME}/diskuv-ocaml") +else() + set(DKML_DATA_HOME "$ENV{HOME}/.local/share/diskuv-ocaml") +endif() + +# DkSDK data home +if(WIN32) + set(DKSDK_DATA_HOME "$ENV{LOCALAPPDATA}/Programs/DkSDK") +elseif(DEFINED ENV{XDG_DATA_HOME}) + set(DKSDK_DATA_HOME "$ENV{XDG_DATA_HOME}/dksdk") +else() + set(DKSDK_DATA_HOME "$ENV{HOME}/.local/share/dksdk") +endif() + +# Splat DKTOOL_CMDLINE +cmake_language(EVAL CODE "parse_dktool_command_line(${DKTOOL_CMDLINE})") diff --git a/dk b/dk new file mode 100755 index 0000000..79ad1d6 --- /dev/null +++ b/dk @@ -0,0 +1,717 @@ +#!/bin/sh +########################################################################## +# File: dktool/dk # +# # +# Copyright 2023 Diskuv, Inc. # +# # +# Licensed under the Open Software License version 3.0 # +# (the "License"); you may not use this file except in compliance # +# with the License. You may obtain a copy of the License at # +# # +# https://opensource.org/license/osl-3-0-php/ # +# # +########################################################################## + +# Recommendation: Place this file in source control. +# Auto-generated by `./dk dksdk.project.new` of dktool. +# +# Invoking: ./dk +# That works in Powershell on Windows, and in Unix. Copy-and-paste works! +# +# Purpose: Install CMake if not already. Then invoke CMake. + +set -euf + +dk_pwd=$PWD + +# --- Imports of dkml-runtime-common's crossplatform-functions.sh --- + +# Get standard locations of Unix system binaries like `/usr/bin/mv` (or `/bin/mv`). +# +# Will not return anything in `/usr/local/bin` or `/usr/sbin`. Use when you do not +# know whether the PATH has been set correctly, or when you do not know if the +# system binary exists. +# +# At some point in the future, this function will error out if the required system binaries +# do not exist. Most system binaries are common to all Unix/Linux/macOS installations but +# some (like `comm`) may need to be installed for proper functioning of DKML. +# +# Outputs: +# - env:DKMLSYS_MV - Location of `mv` +# - env:DKMLSYS_CHMOD - Location of `chmod` +# - env:DKMLSYS_UNAME - Location of `uname` +# - env:DKMLSYS_ENV - Location of `env` +# - env:DKMLSYS_AWK - Location of `awk` +# - env:DKMLSYS_SED - Location of `sed` +# - env:DKMLSYS_COMM - Location of `comm` +# - env:DKMLSYS_INSTALL - Location of `install` +# - env:DKMLSYS_RM - Location of `rm` +# - env:DKMLSYS_SORT - Location of `sort` +# - env:DKMLSYS_CAT - Location of `cat` +# - env:DKMLSYS_STAT - Location of `stat` +# - env:DKMLSYS_GREP - Location of `grep` +# - env:DKMLSYS_CURL - Location of `curl` (empty if not found) +# - env:DKMLSYS_WGET - Location of `wget` (empty if not found) +# - env:DKMLSYS_TR - Location of `tr` +autodetect_system_binaries() { + if [ -z "${DKMLSYS_MV:-}" ]; then + if [ -x /usr/bin/mv ]; then + DKMLSYS_MV=/usr/bin/mv + else + DKMLSYS_MV=/bin/mv + fi + fi + if [ -z "${DKMLSYS_CHMOD:-}" ]; then + if [ -x /usr/bin/chmod ]; then + DKMLSYS_CHMOD=/usr/bin/chmod + else + DKMLSYS_CHMOD=/bin/chmod + fi + fi + if [ -z "${DKMLSYS_UNAME:-}" ]; then + if [ -x /usr/bin/uname ]; then + DKMLSYS_UNAME=/usr/bin/uname + else + DKMLSYS_UNAME=/bin/uname + fi + fi + if [ -z "${DKMLSYS_ENV:-}" ]; then + if [ -x /usr/bin/env ]; then + DKMLSYS_ENV=/usr/bin/env + else + DKMLSYS_ENV=/bin/env + fi + fi + if [ -z "${DKMLSYS_AWK:-}" ]; then + if [ -x /usr/bin/awk ]; then + DKMLSYS_AWK=/usr/bin/awk + else + DKMLSYS_AWK=/bin/awk + fi + fi + if [ -z "${DKMLSYS_SED:-}" ]; then + if [ -x /usr/bin/sed ]; then + DKMLSYS_SED=/usr/bin/sed + else + DKMLSYS_SED=/bin/sed + fi + fi + if [ -z "${DKMLSYS_COMM:-}" ]; then + if [ -x /usr/bin/comm ]; then + DKMLSYS_COMM=/usr/bin/comm + else + DKMLSYS_COMM=/bin/comm + fi + fi + if [ -z "${DKMLSYS_INSTALL:-}" ]; then + if [ -x /usr/bin/install ]; then + DKMLSYS_INSTALL=/usr/bin/install + else + DKMLSYS_INSTALL=/bin/install + fi + fi + if [ -z "${DKMLSYS_RM:-}" ]; then + if [ -x /usr/bin/rm ]; then + DKMLSYS_RM=/usr/bin/rm + else + DKMLSYS_RM=/bin/rm + fi + fi + if [ -z "${DKMLSYS_SORT:-}" ]; then + if [ -x /usr/bin/sort ]; then + DKMLSYS_SORT=/usr/bin/sort + else + DKMLSYS_SORT=/bin/sort + fi + fi + if [ -z "${DKMLSYS_CAT:-}" ]; then + if [ -x /usr/bin/cat ]; then + DKMLSYS_CAT=/usr/bin/cat + else + DKMLSYS_CAT=/bin/cat + fi + fi + if [ -z "${DKMLSYS_STAT:-}" ]; then + if [ -x /usr/bin/stat ]; then + DKMLSYS_STAT=/usr/bin/stat + else + DKMLSYS_STAT=/bin/stat + fi + fi + if [ -z "${DKMLSYS_GREP:-}" ]; then + if [ -x /usr/bin/grep ]; then + DKMLSYS_GREP=/usr/bin/grep + else + DKMLSYS_GREP=/bin/grep + fi + fi + if [ -z "${DKMLSYS_CURL:-}" ]; then + if [ -x /usr/bin/curl ]; then + DKMLSYS_CURL=/usr/bin/curl + elif [ -x /bin/curl ]; then + DKMLSYS_CURL=/bin/curl + else + DKMLSYS_CURL= + fi + fi + if [ -z "${DKMLSYS_WGET:-}" ]; then + if [ -x /usr/bin/wget ]; then + DKMLSYS_WGET=/usr/bin/wget + elif [ -x /bin/wget ]; then + DKMLSYS_WGET=/bin/wget + else + DKMLSYS_WGET= + fi + fi + if [ -z "${DKMLSYS_TR:-}" ]; then + if [ -x /usr/bin/tr ]; then + DKMLSYS_TR=/usr/bin/tr + else + DKMLSYS_TR=/bin/tr + fi + fi + export DKMLSYS_MV DKMLSYS_CHMOD DKMLSYS_UNAME DKMLSYS_ENV DKMLSYS_AWK DKMLSYS_SED DKMLSYS_COMM DKMLSYS_INSTALL + export DKMLSYS_RM DKMLSYS_SORT DKMLSYS_CAT DKMLSYS_STAT DKMLSYS_GREP DKMLSYS_CURL DKMLSYS_WGET DKMLSYS_TR +} + +# Is a Windows build machine if we are in a MSYS2 or Cygwin environment. +# +# Better alternatives +# ------------------- +# +# 1. If you are checking to see if you should do a cygpath, then just guard it +# like so: +# if [ -x /usr/bin/cygpath ]; then +# do_something $(/usr/bin/cygpath ...) ... +# fi +# This clearly guards what you are about to do (cygpath) with what you will +# need (cygpath). +# 2. is_arg_windows_platform +is_unixy_windows_build_machine() { + if is_msys2_msys_build_machine || is_cygwin_build_machine; then + return 0 + fi + return 1 +} + +# Is a MSYS2 environment with the MSYS or MINGW64 subsystem? +# * MSYS2 can also do MinGW 32-bit and 64-bit subsystems. Used by Diskuv OCaml +# * MINGW64 used by Git Bash (aka. GitHub Actions `shell: bash`) +# https://www.msys2.org/docs/environments/ +is_msys2_msys_build_machine() { + if [ -e /usr/bin/msys-2.0.dll ] && { + [ "${MSYSTEM:-}" = "MSYS" ] || [ "${MSYSTEM:-}" = "MINGW64" ] || [ "${MSYSTEM:-}" = "UCRT64" ] || [ "${MSYSTEM:-}" = "CLANG64" ] || [ "${MSYSTEM:-}" = "MINGW32" ] || [ "${MSYSTEM:-}" = "CLANG32" ] || [ "${MSYSTEM:-}" = "CLANGARM64" ] + }; then + return 0 + fi + return 1 +} + +is_cygwin_build_machine() { + if [ -e /usr/bin/cygwin1.dll ]; then + return 0 + fi + return 1 +} + +# Tries to find the host ABI. +# +# Beware: This function uses `uname` probing which is inaccurate during +# cross-compilation. +# +# Outputs: +# - env:BUILDHOST_ARCH will contain the host ABI. +autodetect_buildhost_arch() { + # Set DKMLSYS_* + autodetect_system_binaries + + autodetect_buildhost_arch_SYSTEM=$("$DKMLSYS_UNAME" -s) + autodetect_buildhost_arch_MACHINE=$("$DKMLSYS_UNAME" -m) + # list from https://en.wikipedia.org/wiki/Uname and https://stackoverflow.com/questions/45125516/possible-values-for-uname-m + case "${autodetect_buildhost_arch_SYSTEM}-${autodetect_buildhost_arch_MACHINE}" in + Linux-armv7*) + BUILDHOST_ARCH=linux_arm32v7;; + Linux-armv6* | Linux-arm) + BUILDHOST_ARCH=linux_arm32v6;; + Linux-aarch64 | Linux-arm64 | Linux-armv8*) + BUILDHOST_ARCH=linux_arm64;; + Linux-i386 | Linux-i686) + BUILDHOST_ARCH=linux_x86;; + Linux-x86_64) + BUILDHOST_ARCH=linux_x86_64;; + Darwin-arm64) + BUILDHOST_ARCH=darwin_arm64;; + Darwin-x86_64) + BUILDHOST_ARCH=darwin_x86_64;; + *-i386 | *-i686) + if is_unixy_windows_build_machine; then + BUILDHOST_ARCH=windows_x86 + else + printf "%s\n" "FATAL: Unsupported build machine type obtained from 'uname -s' and 'uname -m': $autodetect_buildhost_arch_SYSTEM and $autodetect_buildhost_arch_MACHINE" >&2 + exit 1 + fi + ;; + *-x86_64) + if is_unixy_windows_build_machine; then + BUILDHOST_ARCH=windows_x86_64 + else + printf "%s\n" "FATAL: Unsupported build machine type obtained from 'uname -s' and 'uname -m': $autodetect_buildhost_arch_SYSTEM and $autodetect_buildhost_arch_MACHINE" >&2 + exit 1 + fi + ;; + *) + # Since: + # 1) MSYS2 does not run on ARM/ARM64 (https://www.msys2.org/docs/environments/) + # 2) MSVC does not use ARM/ARM64 as host machine (https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160) + # we do not support Windows ARM/ARM64 as a build machine + printf "%s\n" "FATAL: Unsupported build machine type obtained from 'uname -s' and 'uname -m': $autodetect_buildhost_arch_SYSTEM and $autodetect_buildhost_arch_MACHINE" >&2 + exit 1 + ;; + esac +} + +# A function that will try to print an ISO8601 timestamp, but will fallback to +# the system default. Always uses UTC timezone. +try_iso8601_timestamp() { + date -u -Iseconds 2>/dev/null || TZ=UTC date +} + +# A function that will print the command and possibly time it (if and only if it uses a full path to +# an executable, so that 'time' does not fail on internal shell functions). +# If --return-error-code is the first argument or LOG_TRACE_RETURN_ERROR_CODE=ON, then instead of exiting the +# function will return the error code. +log_trace() { + log_trace_RETURN=${LOG_TRACE_RETURN_ERROR_CODE:-OFF} + + log_trace_1="$1" + if [ "$log_trace_1" = "--return-error-code" ]; then + shift + log_trace_RETURN=ON + fi + + if [ "${DKML_BUILD_TRACE:-OFF}" = ON ]; then + printf "[%s] %s\n" "$(try_iso8601_timestamp)" "+ $*" >&2 + if [ -x "$1" ]; then + time "$@" + else + "$@" + fi + else + # use judgement so we essentially have log at an INFO level + case "$1" in + rm|cp) + # debug level. only show when DKML_BUILD_TRACE=ON + ;; + git|make|ocaml_configure|ocaml_make|make_host|make_target) + # info level. and can show entire command without polluting the screen + printf "[%s] %s\n" "$(try_iso8601_timestamp)" "$*" >&2 + ;; + *) printf "[%s] %s\n" "$(try_iso8601_timestamp)" "$1" >&2 + esac + "$@" + fi + log_trace_ec="$?" + if [ "$log_trace_ec" -ne 0 ]; then + if [ "$log_trace_RETURN" = ON ]; then + return "$log_trace_ec" + else + printf "FATAL: Command failed with exit code %s: %s\n" "$log_trace_ec" "$*" >&2 + exit "$log_trace_ec" + fi + fi +} + +# [sha256compute FILE] writes the SHA256 checksum (hex encoded) of file FILE to the standard output. +sha256compute() { + sha256compute_FILE="$1" + shift + # For reasons unclear doing the following in MSYS2: + # sha256sum 'Z:\source\README.md' + # will produce a backslash like: + # \5518c76ed7234a153941fb7bc94b6e91d9cb8f1c4e22daf169a59b5878c3fc8a *Z:\\source\\README.md + # So always cygpath the filename if available + if [ -x /usr/bin/cygpath ]; then + sha256compute_FILE=$(/usr/bin/cygpath -a "$sha256compute_FILE") + fi + + if [ -x /usr/bin/shasum ]; then + /usr/bin/shasum -a 256 "$sha256compute_FILE" | awk '{print $1}' + elif [ -x /usr/bin/sha256sum ]; then + /usr/bin/sha256sum "$sha256compute_FILE" | awk '{print $1}' + else + printf "FATAL: %s\n" "No sha256 checksum utility found" >&2 + exit 107 + fi +} + +# [sha256check FILE SUM] checks that the file FILE has a SHA256 checksum (hex encoded) of SUM. +# The function will return nonzero (and exit with failure if `set -e` is enabled) if the checksum does not match. +sha256check() { + sha256check_FILE="$1" + shift + sha256check_SUM="$1" + shift + if [ -x /usr/bin/shasum ]; then + printf "%s %s" "$sha256check_SUM" "$sha256check_FILE" | /usr/bin/shasum -a 256 -c >&2 + elif [ -x /usr/bin/sha256sum ]; then + printf "%s %s" "$sha256check_SUM" "$sha256check_FILE" | /usr/bin/sha256sum -c >&2 + else + printf "FATAL: %s\n" "No sha256 checksum utility found" >&2 + exit 107 + fi +} + +# [downloadfile URL FILE SUM] downloads from URL into FILE and verifies the SHA256 checksum of SUM. +# If the FILE already exists with the correct checksum it is not redownloaded. +# The function will exit with failure if the checksum does not match. +downloadfile() { + downloadfile_URL="$1" + shift + downloadfile_FILE="$1" + shift + downloadfile_SUM="$1" + shift + + # Set DKMLSYS_* + autodetect_system_binaries + + if [ -e "$downloadfile_FILE" ]; then + if sha256check "$downloadfile_FILE" "$downloadfile_SUM"; then + return 0 + else + $DKMLSYS_RM -f "$downloadfile_FILE" + fi + fi + if [ "${CI:-}" = true ]; then + if [ -n "$DKMLSYS_CURL" ]; then + log_trace "$DKMLSYS_CURL" -L -s "$downloadfile_URL" -o "$downloadfile_FILE".tmp + elif [ -n "$DKMLSYS_WGET" ]; then + log_trace "$DKMLSYS_WGET" -q -O "$downloadfile_FILE".tmp "$downloadfile_URL" + else + echo "No curl or wget available on the system paths" >&2 + exit 107 + fi + else + if [ -n "$DKMLSYS_CURL" ]; then + log_trace "$DKMLSYS_CURL" -L "$downloadfile_URL" -o "$downloadfile_FILE".tmp + elif [ -n "$DKMLSYS_WGET" ]; then + log_trace "$DKMLSYS_WGET" -O "$downloadfile_FILE".tmp "$downloadfile_URL" + else + echo "No curl or wget available on the system paths" >&2 + exit 107 + fi + fi + if ! sha256check "$downloadfile_FILE".tmp "$downloadfile_SUM"; then + printf "%s\n" "FATAL: Encountered a corrupted or compromised download from $downloadfile_URL" >&2 + exit 1 + fi + $DKMLSYS_MV "$downloadfile_FILE".tmp "$downloadfile_FILE" +} + +# --- Environment detection --- + +# Set DKMLSYS_* +autodetect_system_binaries + +# Find host ABI. Set in BUILDHOST_ARCH +autodetect_buildhost_arch + +# Use the project tree as the current directory +PROJ_DIR=$(dirname "$0") +PROJ_DIR=$(cd "$PROJ_DIR" && pwd) +cd "$PROJ_DIR" + +# --- Tool directory selection --- + +tools_dir= +tools_name=dktool +# 1. Check if CI since many CI providers can only cache content in a subdirectory +# of the project. +if [ -z "$tools_dir" ] && [ "${CI:-}" = true ]; then + install -d "$PROJ_DIR/.tools" + tools_dir="$PROJ_DIR/.tools" +fi +# 2. Check in locations rooted under /opt/diskuv +# We look under a /opt/diskuv early because +# - Especially important for WSL2 to use a pure Linux filesystem (ext4) for +# best performance. +# - Using a canonical location (especially /opt/diskuv/usr/share) makes +# it easy to use CMake presets for non-Windows hosts. +if [ -z "$tools_dir" ] && [ -n "${XDG_DATA_HOME:-}" ] && [ -w "/opt/diskuv/$XDG_DATA_HOME" ]; then + install -d "/opt/diskuv/$XDG_DATA_HOME/$tools_name" + tools_dir="/opt/diskuv/$XDG_DATA_HOME/$tools_name" +fi +if [ -z "$tools_dir" ] && [ -n "${HOME:-}" ] && [ -w "/opt/diskuv/$HOME" ]; then + install -d "/opt/diskuv/$HOME/.local/share/$tools_name" + tools_dir="/opt/diskuv/$HOME/.local/share/$tools_name" +fi +if [ -z "$tools_dir" ] && [ -w "/opt/diskuv/usr/share" ]; then + install -d "/opt/diskuv/usr/share/$tools_name" + tools_dir="/opt/diskuv/usr/share/$tools_name" +fi +# 3. Check in the conventional locations rooted under / +if [ -z "$tools_dir" ] && [ -n "${XDG_DATA_HOME:-}" ] && [ -w "$XDG_DATA_HOME" ]; then + install -d "$XDG_DATA_HOME/$tools_name" + tools_dir="$XDG_DATA_HOME/$tools_name" +fi +if [ -z "$tools_dir" ] && [ -n "${HOME:-}" ] && [ -w "$HOME" ]; then + install -d "$HOME/.local/share/$tools_name" + tools_dir="$HOME/.local/share/$tools_name" +fi +# 4. Validate +if [ -z "$tools_dir" ]; then + echo "FATAL: Could not find a location to install the tools necessary for this project." >&2 + echo " ...: Make sure you have a home directory and that it is write-able, or define" >&2 + echo " ...: the environment variable XDG_DATA_HOME in a shell profile script after" >&2 + echo " ...: creating the \$XDG_DATA_HOME directory." >&2 + exit 2 +fi + +# --- Tool downloads and installs --- + +# PREREQS +# ------- + +install_linux_prog() { + install_linux_prog_NAME=$1 + shift + install_linux_prog_PKG=$1 + shift + if [ -x "/usr/bin/$install_linux_prog_NAME" ]; then + export DK_PROG_INSTALLED_LOCATION="/usr/bin/$install_linux_prog_NAME" + else + if command -v yum > /dev/null 2> /dev/null; then + if [ "$(id -u)" -eq 0 ]; then + yum install -y "$install_linux_prog_PKG" + else + echo "Running: sudo yum install -y $install_linux_prog_PKG" + sudo yum install -y "$install_linux_prog_PKG" + fi + else + if [ "$(id -u)" -eq 0 ]; then + apt-get -q install -y "$install_linux_prog_PKG" + else + echo "Running: sudo -q apt-get -qq install -y --no-install-suggests $install_linux_prog_PKG" + sudo apt-get -qq install -y --no-install-suggests "$install_linux_prog_PKG" + fi + fi + DK_PROG_INSTALLED_LOCATION=$(command -v "$install_linux_prog_NAME") + export DK_PROG_INSTALLED_LOCATION + fi +} + +get_homebrew_binary() { + get_homebrew_binary_NAME=$1 + shift + if command -v brew > /dev/null 2> /dev/null; then + get_homebrew_binary_PREFIX=$(brew --prefix) + if [ -x "$get_homebrew_binary_PREFIX/bin/$get_homebrew_binary_NAME" ]; then + export DK_PROG_INSTALLED_LOCATION="$get_homebrew_binary_PREFIX/bin/$get_homebrew_binary_NAME" + return 0 + fi + fi + return 1 +} + +install_macos_prog() { + install_macos_prog_NAME=$1 + shift + install_macos_prog_PKG=$1 + shift + if [ -x "/usr/bin/$install_macos_prog_NAME" ]; then + export DK_PROG_INSTALLED_LOCATION="/usr/bin/$install_macos_prog_NAME" + elif [ -x "/usr/local/bin/$install_macos_prog_NAME" ]; then + export DK_PROG_INSTALLED_LOCATION="/usr/local/bin/$install_macos_prog_NAME" + elif get_homebrew_binary "$install_macos_prog_NAME"; then + # DK_PROG_INSTALLED_LOCATION already set by [get_homebrew_binary] + true + else + if command -v brew > /dev/null 2> /dev/null; then + brew install --quiet --formula "$install_macos_prog_PKG" >&2 + get_homebrew_binary "$install_macos_prog_NAME" + # DK_PROG_INSTALLED_LOCATION already set by [get_homebrew_binary] + elif command -v port > /dev/null 2> /dev/null; then + if [ "$(id -u)" -eq 0 ]; then + port install "$install_macos_prog_PKG" + else + echo "Running: sudo port install $install_macos_prog_PKG" + sudo port install "$install_macos_prog_PKG" + fi + DK_PROG_INSTALLED_LOCATION=$(command -v "$install_macos_prog_NAME") + export DK_PROG_INSTALLED_LOCATION + else + echo "FATAL: Neither Homebrew nor MacPorts are available on your macOS. You can follow https://docs.brew.sh/Installation to install Homebrew." >&2 + exit 2 + fi + fi +} + +case $BUILDHOST_ARCH in + linux_*) + install_linux_prog wget wget # For [downloadfile] + install_linux_prog tar tar # For handling tar balls later in this script + ;; +esac + +# NINJA +# ----- +# We need a valid CMAKE_GENERATOR to do FetchContent_Populate() in script mode + +NINJA_EXE= +case $BUILDHOST_ARCH in + darwin_*) + install_macos_prog ninja ninja + NINJA_EXE=$DK_PROG_INSTALLED_LOCATION;; + linux_*) + install_linux_prog ninja ninja-build + NINJA_EXE=$DK_PROG_INSTALLED_LOCATION;; +esac + +# CMAKE +# ----- + +case $BUILDHOST_ARCH in + linux_*) + # This is for CMake to do FetchContent() + install_linux_prog git git ;; +esac +case $BUILDHOST_ARCH in + linux_x86) + # This is for Python wheel extraction + install_linux_prog unzip unzip ;; +esac + +cmake_base= +cmake_majmin_ver=3.25 +cmake_majminpat_ver=3.25.2 +cmake_bindir= +cmake_destdir=$tools_dir/cmake-$cmake_majminpat_ver +install -d "$tools_dir/dl" +download_cmake() { + case $BUILDHOST_ARCH in + darwin_*) + install_macos_prog cmake cmake + cmake_bindir=$(dirname "$DK_PROG_INSTALLED_LOCATION") + ;; + linux_x86_64) + cmake_base="cmake-$cmake_majminpat_ver-linux-x86_64" + printf "%s\n\n" "-- Downloading cmake-$cmake_majminpat_ver for Linux x86_64" >&2 + downloadfile \ + "https://github.com/Kitware/CMake/releases/download/v$cmake_majminpat_ver/$cmake_base.tar.gz" \ + "$tools_dir/dl/cmake.tar.gz" \ + 783da74f132fd1fea91b8236d267efa4df5b91c5eec1dea0a87f0cf233748d99 + cmake_bindir="$cmake_destdir/bin" ;; + linux_x86) + # CMake does not provide 32-bit binaries. But pypi does at https://pypi.org/project/cmake/ + printf "%s\n\n" "-- Downloading cmake-$cmake_majminpat_ver for Linux x86" >&2 + downloadfile \ + https://files.pythonhosted.org/packages/11/6e/aeeddf2f5b16542b6a30ceab4896421e8705d8e9a9296dba79395db11b00/cmake-3.25.2-py2.py3-none-manylinux_2_17_i686.manylinux2014_i686.whl \ + "$tools_dir/dl/cmake.whl" \ + 715ef82e81b48db3e4c7744614c15ff361d53f6987fd70b1b66b0880595f2e2c + cmake_bindir="$cmake_destdir/bin" ;; + linux_arm64) + cmake_base="cmake-$cmake_majminpat_ver-linux-aarch64" + printf "%s\n\n" "-- Downloading cmake-$cmake_majminpat_ver for Linux ARM64" >&2 + downloadfile \ + "https://github.com/Kitware/CMake/releases/download/v$cmake_majminpat_ver/$cmake_base.tar.gz" \ + "$tools_dir/dl/cmake.tar.gz" \ + 9216ecf0449ade700e66e0def11eeaebf9fa7d4428c02f49cb59f11418d3f8a5 + cmake_bindir="$cmake_destdir/bin" ;; + esac +} +export_cmake_vars() { + case $BUILDHOST_ARCH in + darwin_*) + if command -v brew > /dev/null 2> /dev/null; then + cmake_bindir=$(brew --prefix cmake)/bin + else + cmake_bindir=$(command -v cmake) + cmake_bindir=$(dirname "$cmake_bindir") + fi + ;; + linux_x86_64) + cmake_bindir="$cmake_destdir/bin" ;; + linux_x86) + cmake_bindir="$cmake_destdir/bin" ;; + esac +} +have_correct_cmake=0 +if [ -x "$tools_dir/cmake-$cmake_majminpat_ver/bin/cmake" ]; then + # shellcheck disable=SC2016 + have_correct_cmake_VER=$("$tools_dir/cmake-$cmake_majminpat_ver/bin/cmake" --version | $DKMLSYS_AWK 'NR==1{print $NF}') + if [ "$have_correct_cmake_VER" = "$cmake_majminpat_ver" ]; then + have_correct_cmake=1 + fi +fi +if [ $have_correct_cmake -eq 0 ]; then + download_cmake +fi +# Handle tarball +if [ -e "$tools_dir/dl/cmake.tar.gz" ] && [ -n "$cmake_base" ]; then + rm -rf "$tools_dir/cmake-$cmake_majminpat_ver" + install -d "$cmake_destdir" + tar xCfz "$cmake_destdir" "$tools_dir/dl/cmake.tar.gz" + rm -f "$tools_dir/dl/cmake.tar.gz" + set +f + if [ -e "$cmake_destdir/$cmake_base/CMake.app" ]; then + mv "$cmake_destdir/$cmake_base/CMake.app/Contents"/* "$cmake_destdir/" + else + mv "$cmake_destdir/$cmake_base"/* "$cmake_destdir/" + fi + set -f +fi +# Handle Python wheel +if [ -e "$tools_dir/dl/cmake.whl" ]; then + rm -rf "$tools_dir/cmake-$cmake_majminpat_ver" + cd "$tools_dir/dl" + rm -rf cmake/data + # Don't want cmake/data/{aclocal,bash-completion,emacs,vim} + unzip -q cmake.whl 'cmake/data/bin/**' + unzip -q cmake.whl 'cmake/data/doc/**' + # Don't want cmake/data/share/cmake-$cmake_majmin_ver/Help + unzip -q cmake.whl "cmake/data/share/cmake-$cmake_majmin_ver/"'include/**' + unzip -q cmake.whl "cmake/data/share/cmake-$cmake_majmin_ver/"'Modules/**' + unzip -q cmake.whl "cmake/data/share/cmake-$cmake_majmin_ver/"'Templates/**' + rm -f cmake.whl + cd - + set +f + install -d "$cmake_destdir/share" "$cmake_destdir/doc" "$cmake_destdir/bin" + mv "$tools_dir/dl/cmake/data/bin"/* "$cmake_destdir/bin/" + rm -rf "$cmake_destdir/share/cmake-$cmake_majmin_ver" "$cmake_destdir/doc/cmake-$cmake_majmin_ver" "$cmake_destdir/doc/cmake" + mv "$tools_dir/dl/cmake/data/share/cmake-$cmake_majmin_ver" "$cmake_destdir/share/" + if [ -e "$tools_dir/dl/cmake/data/doc/cmake" ]; then # Windows wheel + mv "$tools_dir/dl/cmake/data/doc/cmake" "$cmake_destdir/doc/" + fi + if [ -e "$tools_dir/dl/cmake/data/doc/cmake-$cmake_majmin_ver" ]; then # Linux wheel + mv "$tools_dir/dl/cmake/data/doc/cmake-$cmake_majmin_ver" "$cmake_destdir/doc/" + fi + set -f + # other dirs: aclocal bash-completion emacs vim + rm -rf "$tools_dir/dl/cmake/data/share" + # be pedantic. if we don't know about a directory, it may be important. so error + # if some directory is non-empty + rmdir "$tools_dir/dl/cmake/data/bin" "$tools_dir/dl/cmake/data/doc" + rmdir "$tools_dir/dl/cmake/data" +fi + +# Put tools in PATH +export_cmake_vars +if [ -n "$cmake_bindir" ] && [ -d "$cmake_bindir" ]; then + tools_bin_dir=$(cd "$cmake_bindir" && pwd) + export PATH="$tools_bin_dir:$PATH" +else + echo "This platform is not supported. No cmake 3.25+ download logic has been added" >&2 + exit 1 +fi + +# Validate +"$cmake_bindir/cmake" --version > /dev/null + +# --- Run finder script --- + +cd "$PROJ_DIR" +"$cmake_bindir/cmake" \ + -D CMAKE_GENERATOR=Ninja -D CMAKE_MAKE_PROGRAM="$NINJA_EXE" \ + -D "DKTOOL_PWD:FILEPATH=$dk_pwd" \ + -D "DKTOOL_WORKDIR:FILEPATH=$tools_dir/work" -D "DKTOOL_CMDLINE:STRING=$*" \ + -P cmake/FindDkToolScripts.cmake diff --git a/dk.cmd b/dk.cmd new file mode 100644 index 0000000..fbee6c9 --- /dev/null +++ b/dk.cmd @@ -0,0 +1,177 @@ +@ECHO OFF + +REM ########################################################################## +REM # File: dktool/dk.cmd # +REM # # +REM # Copyright 2023 Diskuv, Inc. # +REM # # +REM # Licensed under the Open Software License version 3.0 # +REM # (the "License"); you may not use this file except in compliance # +REM # with the License. You may obtain a copy of the License at # +REM # # +REM # https://opensource.org/license/osl-3-0-php/ # +REM # # +REM ########################################################################## + +REM Recommendation: Place this file in source control. +REM Auto-generated by `./dk dksdk.project.new` of dktool. + +REM The canonical way to run this script is: ./dk +REM That works in Powershell on Windows, and in Unix. Copy-and-paste works! + +SETLOCAL + +REM Coding guidelines +REM 1. Microsoft way of getting around PowerShell permissions: +REM https://github.com/microsoft/vcpkg/blob/71422c627264daedcbcd46f01f1ed0dcd8460f1b/bootstrap-vcpkg.bat +REM 2. Write goto downward please so code flow is top to bottom. + +SET DK_CMAKE_VER=3.25.3 +SET DK_NINJA_VER=1.11.1 +SET DK_BUILD_TYPE=Release +SET DK_SHARE=%LOCALAPPDATA%\Programs\DkSDK\dktool +SET DK_PROJ_DIR=%~dp0 +SET DK_PWD=%CD% + +REM -------------- CMAKE -------------- + +REM Find CMAKE.EXE +where.exe /q cmake.exe >NUL 2>NUL +IF %ERRORLEVEL% neq 0 ( + goto FindDownloadedCMake +) +FOR /F "tokens=* usebackq" %%F IN (`where.exe cmake.exe`) DO ( + SET "DK_CMAKE_EXE=%%F" +) + +REM Check if present at /cmake-VER/bin/cmake.exe +:FindDownloadedCMake +IF EXIST %DK_SHARE%\cmake-%DK_CMAKE_VER%-windows-x86_64\bin\cmake.exe ( + SET "DK_CMAKE_EXE=%DK_SHARE%\cmake-%DK_CMAKE_VER%-windows-x86_64\bin\cmake.exe" + GOTO ValidateCMake +) + +REM Download CMAKE.EXE +bitsadmin /transfer dktool-cmake /download /priority FOREGROUND ^ + "https://github.com/Kitware/CMake/releases/download/v%DK_CMAKE_VER%/cmake-%DK_CMAKE_VER%-windows-x86_64.zip" ^ + "%TEMP%\cmake-%DK_CMAKE_VER%-windows-x86_64.zip" +IF %ERRORLEVEL% equ 0 ( + GOTO UnzipCMakeZip +) +REM Try PowerShell 3+ instead +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "Invoke-WebRequest https://github.com/Kitware/CMake/releases/download/v%DK_CMAKE_VER%/cmake-%DK_CMAKE_VER%-windows-x86_64.zip -OutFile '%TEMP%\cmake-%DK_CMAKE_VER%-windows-x86_64.zip'" +IF %ERRORLEVEL% neq 0 ( + echo. + echo.Could not download CMake %DK_CMAKE_VER%. Make sure that PowerShell is installed + echo.and has not been disabled by a corporate policy. + echo. + EXIT /b 1 +) + +REM Unzip CMAKE.EXE (use PowerShell; could download unzip.exe and sha256sum.exe as well in case corporate policy) +:UnzipCMakeZip +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "Expand-Archive '%TEMP%\cmake-%DK_CMAKE_VER%-windows-x86_64.zip' -DestinationPath '%DK_SHARE%'" +IF %ERRORLEVEL% neq 0 ( + echo. + echo.Could not unzip CMake %DK_CMAKE_VER%. Make sure that PowerShell is installed + echo.and has not been disabled by a corporate policy. + echo. + EXIT /b 1 +) +SET "DK_CMAKE_EXE=%DK_SHARE%\cmake-%DK_CMAKE_VER%-windows-x86_64\bin\cmake.exe" + +REM Validate cmake.exe +:ValidateCMake +"%DK_CMAKE_EXE%" -version >NUL 2>NUL +if %ERRORLEVEL% neq 0 ( + echo. + echo.%DK_CMAKE_EXE% + echo.is not responding to the -version option. Make sure that + echo.CMake is installed correctly. + echo. + exit /b 1 +) + +REM -------------- NINJA -------------- + +REM Find NINJA.EXE +where.exe /q ninja.exe >NUL 2>NUL +IF %ERRORLEVEL% neq 0 ( + goto FindDownloadedNinja +) +FOR /F "tokens=* usebackq" %%F IN (`where.exe ninja.exe`) DO ( + SET "DK_NINJA_EXE=%%F" +) + +REM Check if present at /ninja-VER/bin/ninja.exe +:FindDownloadedNinja +IF EXIST %DK_SHARE%\ninja-%DK_NINJA_VER%-windows-x86_64\bin\ninja.exe ( + SET "DK_NINJA_EXE=%DK_SHARE%\ninja-%DK_NINJA_VER%-windows-x86_64\bin\ninja.exe" + GOTO ValidateNinja +) + +REM Download NINJA.EXE +bitsadmin /transfer dktool-ninja /download /priority FOREGROUND ^ + "https://github.com/ninja-build/ninja/releases/download/v%DK_NINJA_VER%/ninja-win.zip" ^ + "%TEMP%\ninja-%DK_NINJA_VER%-windows-x86_64.zip" +IF %ERRORLEVEL% equ 0 ( + GOTO UnzipNinjaZip +) +REM Try PowerShell 3+ instead +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "Invoke-WebRequest https://github.com/ninja-build/ninja/releases/download/v%DK_NINJA_VER%/ninja-win.zip -OutFile '%TEMP%\ninja-%DK_NINJA_VER%-windows-x86_64.zip'" +IF %ERRORLEVEL% neq 0 ( + echo. + echo.Could not download Ninja %DK_NINJA_VER%. Make sure that PowerShell is installed + echo.and has not been disabled by a corporate policy. + echo. + EXIT /b 1 +) + +REM Unzip NINJA.EXE (use PowerShell; could download unzip.exe and sha256sum.exe as well in case corporate policy) +:UnzipNinjaZip +powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "Expand-Archive '%TEMP%\ninja-%DK_NINJA_VER%-windows-x86_64.zip' -DestinationPath '%DK_SHARE%\ninja-%DK_NINJA_VER%-windows-x86_64\bin'" +IF %ERRORLEVEL% neq 0 ( + echo. + echo.Could not unzip Ninja %DK_NINJA_VER%. Make sure that PowerShell is installed + echo.and has not been disabled by a corporate policy. + echo. + EXIT /b 1 +) +SET "DK_NINJA_EXE=%DK_SHARE%\ninja-%DK_NINJA_VER%-windows-x86_64\bin\ninja.exe" + +REM Validate ninja.exe +:ValidateNinja +"%DK_NINJA_EXE%" --version >NUL 2>NUL +if %ERRORLEVEL% neq 0 ( + echo. + echo.%DK_NINJA_EXE% + echo.is not responding to the --version option. Make sure that + echo.Ninja is installed correctly. + echo. + exit /b 1 +) + +REM -------------- DkML PATH --------- +REM We get "git-sh-setup: file not found" in Git for Windows because +REM Command Prompt has the "Path" environment variable, while PowerShell +REM and `with-dkml` use the PATH environment variable. Sadly both +REM can be present in Command Prompt at the same time. Git for Windows +REM (called by FetchContent in CMake) does not comport with what Command +REM Prompt is using. So we let Command Prompt be the source of truth by +REM removing any duplicated PATH twice and resetting to what Command Prompt +REM thinks the PATH is. + +SET _DK_PATH=%PATH% +SET PATH= +SET PATH= +SET PATH=%_DK_PATH% +SET _DK_PATH= + +REM -------------- Run finder -------------- + +cd /d %DK_PROJ_DIR% +"%DK_CMAKE_EXE%" -D CMAKE_GENERATOR=Ninja -D "CMAKE_MAKE_PROGRAM=%DK_NINJA_EXE%" -D "DKTOOL_PWD:FILEPATH=%DK_PWD%" -D "DKTOOL_WORKDIR:FILEPATH=%DK_SHARE%\work" -D "DKTOOL_CMDLINE:STRING=%*" -P cmake/FindDkToolScripts.cmake