#!/usr/bin/env bash # SPDX-License-Identifier: MIT # Copyright 2022 - 2023, Ralf D. Müller and the docToolchain contributors set -e set -u set -o pipefail # The main purpose of the wrapper script is to make 'docToolchain' easy to use. # - it helps to install 'docToolchain' if not installed # - you may manage different 'docToolchain' environments # See https://github.com/docToolchain/docToolchain/releases for available versions. # Set DTC_VERSION to "latest" to get the latest, yet unreleased version. : "${DTC_VERSION:=3.3.1}" # if not set, public docker hub is used : "${DTC_DOCKER_PREFIX:=}" # The 'generateSite' and 'copyThemes' tasks support DTC_SITETHEME, an URL of a theme. # export DTC_SITETHEME=https://....zip # The 'downloadTemplate' tasks uses DTC_TEMPLATE1, DTC_TEMPLATE2, ... # with which you can specify the location of additional templates # export DTC_TEMPLATE1=https://....zip # export DTC_TEMPLATE2=https://....zip # docToolchain configurtion file may be overruled by the user : "${DTC_CONFIG_FILE:=docToolchainConfig.groovy}" # Contains the current project git branch, "-" if not available : "${DTC_PROJECT_BRANCH:=$(git branch --show-current 2> /dev/null || echo -)}" export DTC_PROJECT_BRANCH # DTC_HEADLESS is true if no STDIN is open (i.e. we have an non-interactive shell). : "${DTC_HEADLESS:=$([ -t 0 ] && echo false || echo true)}" export DTC_HEADLESS # Options passed to docToolchain DTC_OPTS="${DTC_OPTS:-} -PmainConfigFile=${DTC_CONFIG_FILE} --warning-mode=none --no-daemon -Dfile.encoding=UTF-8 " # Here you find the project GITHUB_PROJECT_URL=https://github.com/docToolchain/docToolchain GITHUB_PROJECT_URL_SSH=git@github.com:docToolchain/docToolchain # Bump this version up if something is changed in the wrapper script DTCW_VERSION=0.51 # Template replaced by the GitHub value upon releasing dtcw DTCW_GIT_HASH=##DTCW_GIT_HASH## # Exit codes ERR_DTCW=1 ERR_ARG=2 ERR_CODING=3 main() { # For debugging purpose at the top of the script arch=$(uname -m) os=$(uname -s) bash=bash print_version_info assert_argument_exists "$@" [ "${1}" = "--version" ] && exit 0 available_environments=$(get_available_environments) echo "Available docToolchain environments:${available_environments}" dtc_installations=$(get_dtc_installations "${available_environments}" "${DTC_VERSION}") echo "Environments with docToolchain [${DTC_VERSION}]:${dtc_installations}" if is_supported_environment "${1}" && assert_environment_available "${1}" "${DTC_VERSION}"; then # User enforced environment environment=${1} shift 1 assert_argument_exists "$@" else environment=$(get_environment "$@") fi echo "Using environment: ${environment}" if [ "${1}" = "install" ]; then install_component_and_exit "${environment}" "${2-}" elif [ "${1}" = "getJava" ]; then # TODO: remove getJava in the next major release echo "Warning: 'getJava' is deprecated and and will be removed. Use './dtcw install java' instead." install_component_and_exit "${environment}" "java" fi # No install command, so forward call to docToolchain but first we check if # everything is there. docker_image_name="" docker_extra_arguments="" if [[ ${environment} != docker ]]; then assert_doctoolchain_installed "${environment}" "${DTC_VERSION}" assert_java_version_supported # TODO: what if 'doctoolchain' found by $PATH does not match the one from the local environment? # The version provided by $DTC_VERSION could be a different one. else docker_image_name="doctoolchain/doctoolchain" if [ "${1}" = "image" ]; then docker_image_name="${2-}" shift 2 assert_argument_exists "$@" fi echo "Using docker image: ${docker_image_name}" if [ "${1}" = "extra_arguments" ]; then docker_extra_arguments="${2-}" shift 2 assert_argument_exists "$@" echo "Extra arguments passed to 'docker run' ${docker_extra_arguments}" fi fi command=$(build_command "${environment}" "${DTC_VERSION}" "${docker_image_name}" "${docker_extra_arguments}" "$@") [[ "${DTC_HEADLESS}" = true ]] && echo "Using headless mode since there is no (terminal) interaction possible" show_os_related_info emu="" if [ "${os}" = "Darwin" ] && [ "${arch}" = "arm64" ]; then echo "Apple silicon detected, using x86_64 mode and os native bash" emu="/usr/bin/arch -x86_64" bash="/bin/bash" fi # echo "Command to invoke: ${command}" exec ${emu} ${bash} -c "${command}" } assert_argument_exists() { if [[ $# -lt 1 ]]; then error "argument missing" usage exit ${ERR_ARG} fi } error() { printf "\nError: %s\n\n" "${1}" >&2 } usage() { cat < /dev/null || echo "unknown") echo "docToolchain ${DTC_VERSION} - ${dtc_git_hash}" else echo "docToolchain ${DTC_VERSION}" fi echo "OS/arch: ${os}/${arch}" } get_available_environments() { # The local environment is alway available - even if docToolchain is not installed local envs=" local" # 'sdk' is provided a bash founction - thus command doesn't work if [ -n "${SDKMAN_DIR:-}" ] && [ -s "${SDKMAN_DIR}/bin/sdkman-init.sh" ]; then envs+=" sdk" fi if has docker; then envs+=" docker" fi echo "${envs}" } has() { command -v "$1" 1>/dev/null 2>&1 } get_dtc_installations() { local envs=${1} local version=${2} local installations="" if [ -x "${DTC_HOME}/bin/doctoolchain" ]; then installations+=" local" else # maybe it is just available in the path if command -v doctoolchain >/dev/null 2>&1; then installations+=" local" fi fi if [[ "${envs}" =~ sdk ]] && sdk_home_doctoolchain "${version}" &> /dev/null ; then installations+=" sdk" fi if [[ "${envs}" =~ docker ]]; then # Having docker installed means docToolchain is available installations+=" docker" fi [ -z "${installations}" ] && installations=" none" echo "${installations}" } sdk_home_doctoolchain() { local version=${1} local sdk_doctoolchain="${SDKMAN_DIR}/candidates/doctoolchain/${version}" # Don't use `sdk home doctoolchain ${version}` - see https://github.com/sdkman/sdkman-cli/issues/1196 if [[ -x "${sdk_doctoolchain}/bin/doctoolchain" ]]; then printf "%s" "${sdk_doctoolchain}" return 0 fi printf "doctoolchain %s is not installed on your system" "${version}" 1>&2 return 1 } is_supported_environment() { local supported_environments=("local" "sdk" "docker") [[ "${supported_environments[*]}" =~ ${1} ]] } assert_environment_available() { local env=${1} local version=${2} # If environment not available, exit with error if ! is_environment_available "${env}" ; then error "argument error - environment '${env}' not available" if [[ "${env}" == "sdk" ]]; then printf "%s\n" \ "Install SDKMAN! (https://sdkman.io) with" \ "" \ " $ curl -s \"https://get.sdkman.io\" | bash" \ "" \ "Then open a new shell and install 'docToolchain' with" \ " $ sdk install doctoolchain ${version}" else echo "Install 'docker' on your host to execute docToolchain in a container." fi echo exit ${ERR_ARG} fi if is_doctoolchain_development_version "${version}" && [ "${env}" != local ] ; then error "argument error - invalid environment '${env}'." echo "Development version '${version}' can only be used in a local environment." echo exit ${ERR_ARG} fi } is_environment_available() { [[ "${available_environments}" =~ ${1} ]] } is_doctoolchain_development_version() { local version=${1} # Is 'latest' a good name? It maybe interpreted as latest stable release. # Alternatives: 'testing', 'dev' (used for development) [ "${version}" == "latest" ] || [ "${version}" == "latestdev" ] } # No environment provided - try to pick the right one get_environment() { # 'install' works only with 'local' environment if [[ "${1}" == install ]] || [[ "${dtc_installations}" =~ none ]]; then echo local return fi # Pick the first one which has an installed docToolchain. # Note: the preference is defined by the order we searched for available environments. for e in ${available_environments}; do if is_doctoolchain_installed "${e}"; then echo "${e}" return fi done } is_doctoolchain_installed() { [[ "${dtc_installations}" =~ ${1} ]] } install_component_and_exit() { local env=${1} local component=${2} if [ -z "${component}" ] ; then error_install_component_and_die "component missing" fi exit_code=1 case ${env} in local) case ${component} in doctoolchain) local_install_doctoolchain "${DTC_VERSION}" assert_java_version_supported ;; java) local_install_java ;; *) error_install_component_and_die "unknown component '${component}'" ;; esac if ! is_doctoolchain_installed "${environment}"; then how_to_install_doctoolchain "${DTC_VERSION}" else printf "%s\n" \ "" \ "Use './dtcw tasks --group doctoolchain' to see docToolchain related tasks." \ "" fi exit_code=0 ;; sdk) error "argument error - '${env} install' not supported." printf "%s\n" \ "To install docToolchain with SDKMAN! execute" \ "" \ " $ sdk install doctoolchain ${DTC_VERSION}" \ "" exit_code=${ERR_ARG} ;; docker) error "argument error - '${env} install' not supported." echo "Executing a task in the 'docker' environment will pull the docToolchain container image." echo exit_code=${ERR_ARG} ;; *) echo "Coding error - not reachable" exit ${ERR_CODING} ;; esac exit ${exit_code} } error_install_component_and_die() { error "${1} - available components are 'doctoolchain', or 'java'" printf "%s\n" \ "Use './dtcw local install doctoolchain' to install docToolchain ${DTC_VERSION}." \ "Use './dtcw local install java' to install a Java version supported by docToolchain." \ "" exit ${ERR_ARG} } local_install_doctoolchain() { local version=${1} if is_doctoolchain_development_version "${version}"; then # User decided to pick a floating version - which means a git clone into the local environment. assert_git_installed "Please install 'git' for working with a 'doctToolchain' development version" if [ -d "${DTC_HOME}/.git" ]; then git -C "${DTC_HOME}" pull echo "Updated docToolchain in local environment to latest version" else local project_url if [ "${version}" == "latest" ]; then project_url="${GITHUB_PROJECT_URL}" else project_url="${GITHUB_PROJECT_URL_SSH}" fi git clone "${project_url}.git" "${DTC_HOME}" echo "Cloned docToolchain in local environment to latest version" fi else if [ -x "${DTC_HOME}/bin/doctoolchain" ]; then echo "Skipped installation of docToolchain: already installed in '${DTC_HOME}'" return fi if ! has unzip; then error "no unzip program installed, exiting…" echo "Install 'unzip' and try to install docToolchain again." return ${ERR_DTCW} fi mkdir -p "${DTC_ROOT}" local distribution_url=${GITHUB_PROJECT_URL}/releases/download/v${version}/docToolchain-${version}.zip download_file "${distribution_url}" "${DTC_ROOT}/source.zip" unzip -q "${DTC_ROOT}/source.zip" -d "${DTC_ROOT}" rm -f "${DTC_ROOT}/source.zip" echo "Installed docToolchain successfully in '${DTC_HOME}'." fi # Add it to the existing installations so the output to guide the user can adjust accordingly. dtc_installations+=" local" } assert_git_installed() { has git && return error "git - command not found" echo "${1}" echo exit ${ERR_DTCW} } download_file() { local url=${1} local file=${2} if has curl; then cmd="curl --fail --location --output $file $url" elif has wget; then # '--show-progress' is not supported in all wget versions (busybox) cmd="wget --quiet --show-progress --output-document=$file $url" elif has fetch; then cmd="fetch --quiet --output=$file $url" else error "no HTTP download program (curl, wget, fetch) found, exiting…" echo "Install either 'curl', 'wget', or 'fetch' and try to install docToolchain again." return ${ERR_DTCW} fi $cmd && return 0 || rc=$? error "Command failed (exit code $rc): ${cmd}" return "${rc}" } assert_java_version_supported() { # Defines the order in which Java is searched. if [ -d "${DTC_JAVA_HOME}" ]; then echo "Caution: Your JAVA_HOME setting is overriden by DTCs own JDK install (for this execution)" if [ -d "${DTC_JAVA_HOME}/Contents" ]; then # JDK for MacOS have a different structure JAVA_HOME="${DTC_JAVA_HOME}/Contents/Home" else JAVA_HOME="${DTC_JAVA_HOME}" fi export JAVA_HOME DTC_OPTS="${DTC_OPTS} '-Dorg.gradle.java.home=${JAVA_HOME}'" JAVA_CMD="${JAVA_HOME}/bin/java" elif [ -n "${JAVA_HOME-}" ]; then JAVA_CMD="${JAVA_HOME}/bin/java" else # Don't provide JAVA_HOME if java is used by PATH. JAVA_CMD=$(command -v java 2>/dev/null) || true fi if [ -z "${JAVA_CMD-}" ] || ! java_version=$("${JAVA_CMD}" -version 2>&1) ; then error "unable to locate a Java Runtime" java_help_and_die fi # We got a Java version java_version=$(echo "$java_version" | awk -F '"' '/version/ {print $0}' | head -1 | cut -d'"' -f2) major_version=$(echo "$java_version" | sed '/^1\./s///' | cut -d'.' -f1) if [ "${major_version}" -ge 11 ] && [ "${major_version}" -le 17 ]; then echo "Using Java ${java_version} [${JAVA_CMD}]" return fi error "unsupported Java version ${major_version} [${JAVA_CMD}]" java_help_and_die } java_help_and_die() { printf "%s\n" \ "docToolchain supports Java versions 11, 14, or 17 (preferred). In case one of those" \ "Java versions is installed make sure 'java' is found with your PATH environment" \ "variable. As alternative you may provide the location of your Java installation" \ "with JAVA_HOME." \ "" \ "Apart from installing Java with the package manager provided by your operating" \ "system, dtcw facilitates the Java installation into a local environment:" \ "" \ " # Install Java in '${DTC_JAVA_HOME}'" \ " $ ./dtcw local install java" \ "" \ "Alternatively you can use SDKMAN! (https://sdkman.io) to manage your Java installations" \ "" if ! is_environment_available sdk; then how_to_install_sdkman "Java 17" fi # TODO: This will break when we change Java version printf "%s\n" \ " $ sdk install java 17.0.7-tem" \ "" \ "If you prefer not to install Java on your host, you can run docToolchain in a" \ "docker container. For this case dtcw provides the 'docker' execution environment." \ "" \ "Example: ./dtcw docker generateSite"\ "" exit ${ERR_DTCW} } how_to_install_sdkman() { printf "%s\n" \ " # First install SDKMAN!" \ " $ curl -s \"https://get.sdkman.io\" | bash" \ " # Then open a new shell and install ${1} with" } local_install_java() { version=17 implementation=hotspot heapsize=normal imagetype=jdk releasetype=ga case "${arch}" in x86_64) arch=x64 ;; arm64) arch=aarch64 ;; esac if [[ ${os} == MINGW* ]]; then error "MINGW64 is not supported" echo "Please use powershell or WSL" exit 1 fi case "${os}" in Linux) os=linux ;; Darwin) os=mac # Enforce usage of Intel Java as long as jbake does not work on Apple Silicon arch=x64 ;; Cygwin) os=linux ;; esac mkdir -p "${DTC_JAVA_HOME}" echo "Downloading JDK Temurin ${version} [${os}/${arch}] from Adoptium to ${DTC_JAVA_HOME}/jdk.tar.gz" local adoptium_java_url="https://api.adoptium.net/v3/binary/latest/${version}/${releasetype}/${os}/${arch}/${imagetype}/${implementation}/${heapsize}/eclipse?project=jdk" download_file "${adoptium_java_url}" "${DTC_JAVA_HOME}/jdk.tar.gz" echo "Extracting JDK from archive file." # TODO: should we not delete a previsouly installed on? # Otherwise we may get a mix of different Java versions. tar -zxf "${DTC_JAVA_HOME}/jdk.tar.gz" --strip 1 -C "${DTC_JAVA_HOME}/." rm -f "${DTC_JAVA_HOME}/jdk/jdk.tar.gz" echo "Successfully installed Java in '${DTC_JAVA_HOME}'." echo } assert_doctoolchain_installed() { local env=${1} local version=${2} if ! is_doctoolchain_installed "${env}"; then # We reach this point if the user executes a command in an # environment where docToolchain is not installed. # Note that 'docker' always has a command (no instalation required) error "doctoolchain - command not found [environment '${env}']" how_to_install_doctoolchain "${version}" exit ${ERR_DTCW} fi } how_to_install_doctoolchain() { local version=${1} printf "%s\n" \ "It seems docToolchain ${version} is not installed. dtcw supports the" \ "following docToolchain environments:" \ "" \ "1. 'local': to install docToolchain in [${DTC_ROOT}] use" \ "" \ " $ ./dtcw local install doctoolchain" \ "" \ "2. 'sdk': to install docToolchain with SDKMAN! (https://sdkman.io)" \ "" if ! is_environment_available sdk; then how_to_install_sdkman docToolchain fi printf "%s\n" \ " \$ sdk install doctoolchain ${version}" \ "" \ "Note that running docToolchain in 'local' or 'sdk' environment needs a" \ "Java runtime (major version 11, 14, or 17) installed on your host." \ "" \ "3. 'docker': pull the docToolchain image and execute docToolchain in a container environment." \ "" \ " \$ ./dtcw docker tasks --group doctoolchain" \ "" } build_command() { local env=${1} local version=${2} local docker_image=${3} local docker_extra_arguments=${4} shift 4 local cmd if [ "${env}" = docker ]; then # TODO: DTC_PROJECT_BRANCH is not passed into the docker environment # See https://github.com/docToolchain/docToolchain/issues/1087 local container_name=doctoolchain-${version} container_name+="-$(date '+%Y%m%d_%H%M%S')" pwd=$(has cygpath && cygpath -w "${PWD}" || echo "${PWD}") docker_env_file=dtcw_docker.env env_file_option="" if [ -f "$docker_env_file" ]; then env_file_option="--env-file ${docker_env_file}" fi docker_args="run --rm -i --platform linux/amd64 -u $(id -u):$(id -g) --name ${container_name} \ -e DTC_HEADLESS=true -e DTC_SITETHEME -e DTC_PROJECT_BRANCH=${DTC_PROJECT_BRANCH} \ ${docker_extra_arguments} ${env_file_option} \ --entrypoint /bin/bash -v '${pwd}:/project' ${DTC_DOCKER_PREFIX}${docker_image}:v${version}" cmd="docker ${docker_args} -c \"doctoolchain . ${*} ${DTC_OPTS} && exit\"" else # TODO: What is this good for? Has probably to do with Docker image creation. if [[ "${DTC_HEADLESS}" = false ]]; then # important for the docker file to find dependencies DTC_OPTS="${DTC_OPTS} '-Dgradle.user.home=${DTC_ROOT}/.gradle'" fi if [ "${env}" = local ]; then # is doctoolchain available on the path? if command -v doctoolchain >/dev/null 2>&1; then cmd="doctoolchain . ${*} ${DTC_OPTS}" else cmd="${DTC_HOME}/bin/doctoolchain . ${*} ${DTC_OPTS}" fi else cmd="$(sdk_home_doctoolchain "${version}")/bin/doctoolchain . ${*} ${DTC_OPTS}" fi fi echo "${cmd}" } show_os_related_info() { # check if we are running on WSL if grep -qsi 'microsoft' /proc/version &> /dev/null ; then # TODO: bug - This URL leads to now-where! printf "%s\n" \ "" \ "Bash is running on WSL which might cause problems with plantUML." \ "See https://doctoolchain.org/docToolchain/v2.0.x/010_manual/50_Frequently_asked_Questions.html#wsl for more details." \ "" fi } # Location where local installations are placed. DTC_ROOT="${HOME}/.doctoolchain" # More than one docToolchain version may be installed locally. # This is the directory for the specific version. DTC_HOME="${DTC_ROOT}/docToolchain-${DTC_VERSION}" # Directory for local Java installation DTC_JAVA_HOME="${DTC_ROOT}/jdk" main "$@"