diff --git a/.idea/openapi-plugin.iml b/.idea/openapi-plugin.iml index 3ef99cd..28bf0d2 100644 --- a/.idea/openapi-plugin.iml +++ b/.idea/openapi-plugin.iml @@ -10,7 +10,7 @@ - + @@ -24,7 +24,7 @@ - + @@ -33,11 +33,11 @@ - + - + diff --git a/breaking_versions.txt b/breaking_versions.txt index ea33d7d..1d93f16 100644 --- a/breaking_versions.txt +++ b/breaking_versions.txt @@ -1,2 +1,3 @@ ## Next line means plugin 2.4.0.RC11 is not compatible with Jeka 0.9.0.RELEASE and above -## 2.4.0.RC11 : 0.9.0.RELEASE (remove this comment and leading '##' to be effective) \ No newline at end of file +## 2.4.0.RC11 : 0.9.0.RELEASE (remove this comment and leading '##' to be effective) +0.11.0-0 : 0.10.49 \ No newline at end of file diff --git a/jeka b/jeka new file mode 100755 index 0000000..3d5e96b --- /dev/null +++ b/jeka @@ -0,0 +1,1071 @@ +#!/bin/bash +# +# Copyright 2014-2024 the original author or authors. +# +# Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Script for launching JeKa tool. +# +# Authors: Jerome Angibaud, Patrick Santana +# +# Rules for selecting a JDK : +# - if JEKA_JDK_HOME env var is specified, select it +# - if a jeka.java.version property is specified +# - if a jeka.jdk.[version] property is specified, select the specified path. +# - else, look in cache or download the proper JDK +# - else +# - if JAVA_HOME env var is specified, select it +# - else, look in cache and download default version (temurin 21) +# +# Rules for reading a property (said "my.prop") : +# - if a command line argument contains "-Dmy.prop=xxx", returns 'xxx' +# - if an OS environment variable 'my.prop' exists, returns this value +# - if property is defined in $BASE_DIR/jeka.properties, returns this value +# - look recursively in $BASE_DIR/../jeka.properties. Stop at first folder ancestor not having a jeka.properties file +# - look in JEKA_USER_HOME/global.properties +# + +set -e +##################### +# Global vars +##################### + +declare CMD_LINE_ARGS=("$@") +declare -a INTERPOLATED_ARGS + +declare JEKA_VERSION_PROP_NAME="jeka.java.version" +declare JEKA_JAR_NAME="dev.jeka.jeka-core.jar" + +declare JEKA_USER_HOME +declare GLOBAL_PROP_FILE +declare BASE_DIR # To find BASE_DIR/jeka/local.properties, BASE_DIR/jeka/def, ... +declare CURRENT_SCRIPT_DIR +declare -i PROGRAM_OPTION_INDEX +CURRENT_SCRIPT_DIR="$( cd "$(dirname "$0")" ; pwd -P )" + +# Global variables are preferred over passing all arguments +# in method call, cause they are too many. +declare DEFAULT_JAVA_VERSION="21" +declare JDK_DOWNLOAD_DISTRIB="temurin" +declare JDK_DOWNLOAD_LIBC_TYPE="glibc" # default for linux, overridden for other os +declare JDK_DOWNLOAD_FILE_TYPE="tar.gz" # overridden for *WIN os +declare JDK_DOWNLOAD_OS +declare JDK_DOWNLOAD_ARCH + +declare IS_VERBOSE # we should not log anything when on the green path, except if '-lsu' arg is present +declare IS_QUIET # we should log only error msg + +declare LOG_DEBUG # DEBUG +declare DRY_RUN # Debugging purpose +declare DEFAULT_BASE_DIR="." + + +####################################### +# Prints passed arguments on the stderr +# Globals: +# none +# Arguments: +# $* +####################################### +msg() { + echo "$*" 1>&2 +} + +####################################### +# Gets the sub-string part ending before '#' of a specified string. ('Hello#World' should returns 'Hello') +# Globals: +# none +# Arguments: +# $1 : the string to extract substring from +# Outputs: +# Write extracted sub-string to stdout +####################################### +substring_before_hash() { + # Extract the substring before '#' using cut + result=$(echo "$1" | cut -d'#' -f1) + + # Echo the resulting substring + echo "$result" +} + +####################################### +# Gets the sub-string part starting after '#' of a specified string. ('Hello#World' should returns 'World') +# Globals: +# none +# Arguments: +# $1 : the string to extract substring from +# Outputs: +# Write extracted sub-string to stdout +####################################### +substring_after_hash() { + # Extract the substring after '#' using parameter expansion + result=${1#*#} + + # If the input string did not have a '#', return empty. Otherwise, return the result + if [ "$result" == "$1" ]; then + echo "" + else + echo "$result" + fi +} + +array_contains() { + local search=$1; shift + local array=("$@") + + for element in "${array[@]}"; do + if [[ $element == $search ]]; then + echo "true" + return 0 # Element found, return success + fi + done + echo "false" +} + + +####################################### +# Gets the value of a property, declared as '-Dprop.name=prop.value' in an array. +# Globals: +# CMD_LINE_ARGS (read) +# Arguments: +# $1 : the property name +# Outputs: +# Write property value to stdout +####################################### +get_system_prop_from_args() { + local prop_name=$1 + local prefix="-D$prop_name=" + for arg in "${CMD_LINE_ARGS[@]}" + do + if [[ "$arg" == "$prefix"* ]] ; then + echo "${arg#$prefix}" + fi + done +} + +####################################### +# Download specified zip/tgz file and unpack it to the specified dir +# Globals: +# none +# Arguments: +# $1 : url to download file +# $2 : the target directory path to unzip/unpack content +# $3 : optional file type to unpack ('zip' or 'tar.gz'). Default is 'zip'. +####################################### +download_and_unpack() { + local url=$1 + local dir=$2 + local file_type=$3 # 'zip' or 'tar.gz' + local temp_file + temp_file=$(mktemp) + rm "$temp_file" + + ## download + if [ -x "$(command -v curl)" ]; then + local silent_flag="s" + if [ "$IS_VERBOSE" != "" ]; then + silent_flag="" + fi + curl -"$silent_flag"Lf --fail --show-error -o "$temp_file" "$url" + if [ $? -ne 0 ]; then + msg "Curl request failed $url" + msg "Returned code: $?" + exit 1 + fi + elif [ -x "$(command -v wget)" ]; then + wget -q -O "$temp_file" "$url" + else + msg "Error: curl or wget not found, please make sure one of them is installed" + exit 1 + fi + + ## unpack + mkdir -p "$dir" + if [ "$file_type" == "tar.gz" ]; then + gzip -cd "$temp_file" | tar xf - -C "$dir" + else + unzip -qq -o "$temp_file" -d "$dir" + fi + rm "$temp_file" +} + +####################################### +# Computes if we should print something on console when downloading files +# Globals: +# CMD_LINE_ARGS (read) +# IS_VERBOSE (write) +# Arguments: +# None +####################################### +compute_VERBOSE_QUIET() { + for arg in "${CMD_LINE_ARGS[@]}" + do + if [ "$arg" == "--verbose" ] || [ "$arg" == "-v" ] || [ "$arg" == "-d" ] || [ "$arg" == "--debug" ]; then + IS_VERBOSE="true" + elif [ "$arg" == "--quiet" ] || [ "$arg" == "-q" ]; then + IS_QUIET="true" + fi + done +} + +####################################### +# Prints passed arguments on the standard stream, only if LOG_DEBUG is non-empty +# Globals: +# LOG_DEBUG (read) +# Arguments: +# $* +####################################### +debug() { + if [ -n "$LOG_DEBUG" ] || [ -n "$IS_VERBOSE" ]; then + echo "$*" 1>&2 + fi +} + +####################################### +# Prints passed arguments on the standard stream, only if IS_QUIET is empty +# Globals: +# IS_QUIET (read) +# Arguments: +# $* +####################################### +info() { + if [ -z "$IS_QUIET" ]; then + echo "$*" 1>&2 + fi +} + +####################################### +# Gets the Jeka directory for the user. This is where are located global.properties and cache dirs. +# Globals: +# JEKA_USER_HOME (read) +# Arguments: +# none +# Outputs: +# Write location to stdout +####################################### +get_jeka_user_home() { + if [ -z "$JEKA_USER_HOME" ]; then + echo "$HOME/.jeka" + else + echo "$JEKA_USER_HOME" + fi +} + +####################################### +# Gets the effective cache dir for Jeka user +# Globals: +# JEKA_CACHE_DIR (read) +# JEKA_USER_HOME (read) +# Arguments: +# none +# Outputs: +# Write location to stdout +####################################### +get_cache_dir() { + if [ -z "$JEKA_CACHE_DIR" ]; then + echo "$JEKA_USER_HOME/cache" + else + echo "$JEKA_CACHE_DIR" + fi +} + +####################################### +# Gets the dir for caching projects cloned from git +# Globals: +# JEKA_CACHE_DIR (read) +# JEKA_USER_HOME (read) +# Arguments: +# none +# Outputs: +# Write location to stdout +####################################### +get_git_cache_dir() { + echo "$(get_cache_dir)/git" +} + + + +####################################### +# Gets the value of a property declared within a property file +# Globals: +# none +# Arguments: +# $1 : the path of the property file +# $2 : the property name +# Outputs: +# Write property value to stdout +####################################### +get_prop_value_from_file() { + local file=$1 + local key=$2 + if [ ! -f "$file" ]; then + return + fi + local value + value=$(grep "^${key}=" "${file}") + local -i key_length + key_length=${#key} + ((key_length++)) + echo "${value:key_length}" +} + +####################################### +# Gets the translation of a property name (as my.prop) to an env var name (as MY_PROP) +# Globals: +# none +# Arguments: +# $1 : the property name +# Outputs: +# Write env var name to stdout +####################################### +get_env_var_name() { + local prop_name="$1" + local result=${prop_name^} + result=${result//./_} + result=${result//-/_} + echo "$result" +} + +####################################### +# Resolves and returns the value of a property by looking in command line args, env var and jeka.properties files +# Globals: +# CMD_LINE_ARGS (read) +# Arguments: +# $1 : the base directory from where looking for jeka.properties file +# $2 : the property name +# Outputs: +# Write env var name to stdout +####################################### +get_prop_value_from_base_dir() { + local base_dir=$1 + local prop_name=$2 + + # First look in command line args + local cmd_args_value + cmd_args_value="$(get_system_prop_from_args "$prop_name")" + if [ "$cmd_args_value" != "" ]; then + echo "$cmd_args_value" + return 0 + fi + + # Then look in env variables + local env_value + env_value=$(printenv "$prop_name") + if [ "$env_value" != "" ]; then + echo "$env_value" + return 0 + fi + + local value + value=$(get_prop_value_from_file "$base_dir/jeka.properties" "$prop_name") + if [ -z "$value" ]; then + local parent_dir="$base_dir/.." + local parent_jeka_props="$parent_dir/jeka.properties" + if [ -f "$parent_jeka_props" ]; then + get_prop_value_from_base_dir "$parent_dir" "$prop_name" + else + get_prop_value_from_file "$GLOBAL_PROP_FILE" "$prop_name" + fi + return 0 + fi + echo "$value" +} + +####################################### +# Returns the JAVA version to use according properties +# Globals: +# CMD_LINE_ARGS (read) +# JEKA_VERSION_PROP_NAME (read) +# Arguments: +# $1 : the base directory from where looking for jeka.properties file +# Outputs: +# Write JAVA version to stdout +####################################### +get_java_version_from_props() { + local base_dir="$1" + local version + version=$(get_prop_value_from_base_dir "$base_dir" "$JEKA_VERSION_PROP_NAME") + local trimmed_version + trimmed_version="${version// /}" # remove spaces + echo "$trimmed_version" +} + +####################################### +# Returns the JAVA version to use according properties +# Globals: +# INTERPOLATED_ARGS (write) +# Arguments: +# $@ : an array representing the original args +# Outputs: +# result in INTERPOLATED_ARGS global args +####################################### +compute_INTERPOLATED_ARGS() { + INTERPOLATED_ARGS=() + for arg in "$@"; do + if [[ $arg == ::* ]]; then # if arg starts with '::' + local token=${arg:2} + local prop_name="jeka.cmd.$token" + local value + value=$(get_prop_value_from_base_dir "$DEFAULT_BASE_DIR" "$prop_name") + if [ "$value" != "" ]; then + + # Value may content 0 or many elements + # shellcheck disable=SC2206 + local substitute=($value) + INTERPOLATED_ARGS+=("${substitute[@]}") + else + INTERPOLATED_ARGS+=("$arg") + fi + else + INTERPOLATED_ARGS+=("$arg") + fi + done +} + +####################################### +# Find the index of the remote option in an an array +# Globals: +# INTERPOLATED_ARGS (write) +# Arguments: +# $@ : an array representing the original args +# Outputs: +# result in INTERPOLATED_ARGS global args +####################################### +find_remote_arg_index() { + index=0 + for arg in "$@"; do + is_remote_arg "$arg" + if [ "$result" == "true" ]; then + echo "$index" + return 0; + fi + ((index++)) + done + echo "-1"; +} + +####################################### +# Determines if the passed argument is a remote option (as -r) +# Arguments: +# $1 : a string representing one argument +# Outputs: +# result in 'result' global args +####################################### +is_remote_arg() { + local arg="$1" + if [[ "$arg" != -* ]]; then + result="false" + return 0 + fi + local option=${arg:1} + + # check if option contains letter 'r' and optionally 'u' or 'p' + if [[ "$option" == *r* ]] && [[ $option =~ ^(r?u?p?|r?u?p?)$ ]] && [[ ${#option} -le 3 ]]; then + result="true" + else + result="false" + fi +} + +# call `get_jdk_home_from_props base_dir JAVA_VERSION` +get_jdk_home_from_props() { + local base_dir=$1 + local jdk_version=$2 + local prop_name="jeka.jdk.$jdk_version" + get_prop_value_from_base_dir "$base_dir" "$prop_name" +} + +is_git_url() { + if [[ $1 =~ ^(https://|ssh://|git://|git@).* ]]; then + echo "true" + else + echo "false" + fi +} + +####################################### +# Returns the dir caching the specified git repo url +# Arguments: +# $1 : a string representing the git repo url +# Outputs: +# The dir location +####################################### +get_folder_name_from_git_url() { + local url=$1 + local trimmed_url=$url + local protocols=("https://" "ssh://" "git://" "git@") + for protocol in "${protocols[@]}"; do + trimmed_url="${trimmed_url#$protocol}" + done + local folder_name="${trimmed_url//\//_}" # replace '/' by '_' + echo "$folder_name" +} + +assert_dir_exits() { + if [ ! -d "$1" ]; then + msg "Directory $1 does not exist" + exit 1 + fi +} + +####################################### +# Computes the base directory according the value of '-r' option. +# If the -r refers to a git repo url, then returns the dir where the repo is cloned. +# Global Vars: +# BASE_DIR (write) +# Arguments: +# $1 : a string representing a git repo url or a directory path +# $2 : true/false for updating the cloned repo +# Outputs: +# result in BASE_DIR global args +####################################### +compute_base_dir_from_resolved_remote_arg() { + + local remote_path="$1" # file-system path or git url + local should_clean="$2" + local is_git_remote + is_git_remote=$(is_git_url "$remote_path") + + # the remote reference a file-system path + if [ "false" == "$is_git_remote" ]; then + + if [[ "$remote_path" == /* ]]; then # absolute path + result="$remote_path" + assert_dir_exits "$result" + else # relative path + result="$(pwd)/$remote_arg" + assert_dir_exits "$result" + result=$(cd "$result" && pwd) # normalize pass + fi + BASE_DIR="$result" + return 0 + fi + + ## Remote reference a git repo + local git_url + git_url=$(substring_before_hash "$remote_path") + local git_tag + git_tag=$(substring_after_hash "$remote_path") + local branch_args="" + if [ "$git_tag" != "" ]; then + branch_args="--branch $git_tag" + fi + local cache_dir_name + cache_dir_name=$(get_folder_name_from_git_url "$remote_path") + result=$(get_git_cache_dir)/"$cache_dir_name" + if [ "$should_clean" == "true" ]; then + rm -rf "$result" + fi + if [ ! -d "$result" ]; then + local quiet_flag="--quiet" + if [ "$IS_VERBOSE" != "" ]; then + quiet_flag="" + fi + info "Cloning $git_url into $result ..." + # $quiet_flag and $branch_args are not doubled-quoted on purpose (they may contains 0 or 2 args) + # shellcheck disable=SC2086 + git clone $quiet_flag -c advice.detachedHead=false --depth 1 $branch_args "$git_url" "$result" + else + debug "Cache directory $result already exists. Won't clone or update." + fi + BASE_DIR=$result +} + +####################################### +# Computes the base directory according presence or not of -r option +# Arguments: +# $@ : array representing the interpolated command line +# Outputs: +# result in BASE_DIR global args +####################################### +compute_BASE_DIR() { + local -a array=( "$@" ) + local result + local index + index=$(find_remote_arg_index "${array[@]}") + if [ "$index" == "-1" ]; then + BASE_DIR=$(pwd) + return 0 + fi + local option=${array[(($index))]} + local next_index=$((index + 1)) + local remote_arg=${array[$next_index]} + local need_update="false" + + # check if cmdline contains -u or --update options, prior the -parameters + local -a prior_program_option_array + if [[ $PROGRAM_OPTION_INDEX == -1 ]]; then + prior_program_option_array=("${INTERPOLATED_ARGS[@]}") + else + prior_program_option_array=("${INTERPOLATED_ARGS[@]:0:PROGRAM_OPTION_INDEX}") + fi + local contains_u + contains_u=$(array_contains "-u" "${prior_program_option_array[@]}") + local contains_update + contains_update=$(array_contains "--update" "${prior_program_option_array[@]}") + + if [[ "$option" == *u* ]] || [[ $contains_u == "true" ]] || [[ $contains_update == "true" ]]; then + need_update="true" + fi + compute_base_dir_from_resolved_remote_arg "$remote_arg" "$need_update" +} + +####################################### +# Computes the location of JeKa distribution directory according the JeKa version +# used for the current BASE DIR. This may implies to dowload the distribution. +# Global Vars: +# JEKA_DIST_DIR (write) +# Arguments: +# $1 : the base directory +# Outputs: +# result in JEKA_DIST_DIR global args +####################################### +compute_JEKA_DIST_DIR() { + if [ "$JEKA_DIST_DIR" != "" ]; then + return 0 + fi + + local base_dir=$1 + local explicit_distrib_dir + explicit_distrib_dir=$(get_prop_value_from_base_dir "$base_dir" "jeka.distrib.location") + if [ -n "$explicit_distrib_dir" ]; then + JEKA_DIST_DIR="$explicit_distrib_dir" + else + local jeka_version= + jeka_version=$(get_prop_value_from_base_dir "$base_dir" "jeka.version") + if [ -z "$jeka_version" ]; then + JEKA_DIST_DIR="$CURRENT_SCRIPT_DIR" # if no version and distrib location specified, use the current script dir + else + local distrib_cache_dir + distrib_cache_dir=$(get_cache_dir)/distributions/$jeka_version + if [ -d "$distrib_cache_dir" ]; then + JEKA_DIST_DIR="$distrib_cache_dir" + + else + # select download repo + local jeka_repo + if [[ "$jeka_version" == *"-SNAPSHOT" ]]; then + jeka_repo="https://oss.sonatype.org/content/repositories/snapshots" + else + jeka_repo="https://repo.maven.apache.org/maven2" + fi + local distrib_repo + distrib_repo=$(get_prop_value_from_base_dir "$base_dir" "jeka.distrib.repo") + [ -n "$distrib_repo" ] && jeka_repo=$distrib_repo + + local url=$jeka_repo/dev/jeka/jeka-core/$jeka_version/jeka-core-$jeka_version-distrib.zip + info "Download Jeka distrib from $url in $distrib_cache_dir" + download_and_unpack "$url" "$distrib_cache_dir" + JEKA_DIST_DIR=$distrib_cache_dir + fi + fi + fi +} + +## Execute Jeka. Call `exec_jeka $base_dir`. +## Returns value in JEKA_CLASSPATH +compute_JEKA_CLASSPATH() { + if [ "$JEKA_CLASSPATH" != "" ]; then + return 0 + fi + local dist_dir=$1 + local bin_dir="$dist_dir" + + # If no distrib dir is specified (no jeka.version specified), we look first + # for jeka-core.jar presence in the same dir of the current script + if [ -z "$dist_dir" ]; then ## No jeka.version is specified, should find the local one + if [ -f "$CURRENT_SCRIPT_DIR/$JEKA_JAR_NAME" ]; then + bin_dir="$CURRENT_SCRIPT_DIR" + fi + if [ -z "$dist_dir" ]; then + msg "No JeKa distribution found from script location $CURRENT_SCRIPT_DIR." + msg "You probably forgot to mention a 'jeka.version' or 'jeka.distrib.location' property in jeka.properties file." + exit 1 + fi + fi + + local boot_dir_args + + ## Reference to remote found + if [ "$REMOTE_BASE_DIR" != "" ]; then + if [ -d "$REMOTE_BASE_DIR/jeka-boot" ]; then + boot_dir_args="$REMOTE_BASE_DIR/jeka-boot/*:" + fi + + ## No remote script, launch on current dir + else + if [ -d "./jeka-boot" ]; then + boot_dir_args="./jeka-boot/*:" + fi + fi + local jar_file="$dist_dir/bin/$JEKA_JAR_NAME" + if [ ! -f "$jar_file" ]; then + jar_file="$dist_dir/$JEKA_JAR_NAME" + fi + if [ ! -f "$jar_file" ]; then + msg "Cannot find JeKa jar file $jar_file." + msg "Are you sure the JeKa distribution you use is properly packaged ?" + exit 1 + fi + JEKA_CLASSPATH="$boot_dir_args$jar_file" +} + +# call `get_or_download_jdk $JAVA_VERSION`. The result is set to DOWNLOAD_JDK_DIR var. +get_or_download_jdk() { + local JAVA_VERSION="$1" + local specified_distrib + specified_distrib=$(get_prop_value_from_base_dir "$BASE_DIR" "jeka.java.distrib") + if [ -n "$specified_distrib" ]; then + JDK_DOWNLOAD_DISTRIB="$specified_distrib" + fi + local jdk_cache_dir + jdk_cache_dir="$(get_cache_dir)/jdks/$JDK_DOWNLOAD_DISTRIB-$JAVA_VERSION" + if [ ! -d "$jdk_cache_dir" ]; then + if [ -z "$JDK_DOWNLOAD_OS" ]; then + msg "Unable to download JDK, unsupported Operating System: $(uname -s)" + msg "You may workaround this problem by specifying a 'jeka.jdk.$JAVA_VERSION' env var or property in ~/jeka/global.properties file." + exit 1 + fi + if [ -z "$JDK_DOWNLOAD_ARCH" ]; then + msg "Unable to download JDK, unsupported Architecture: $(uname -m)" + msg "You may workaround this problem by specifying a 'jeka.jdk.$JAVA_VERSION' env var or property in ~/jeka/global.properties file." + exit 1 + fi + local download_url="https://api.foojay.io/disco/v3.0/directuris?distro=$JDK_DOWNLOAD_DISTRIB&javafx_bundled=false&libc_type=$JDK_DOWNLOAD_LIBC_TYPE&archive_type=$JDK_DOWNLOAD_FILE_TYPE&operating_system=$JDK_DOWNLOAD_OS&package_type=jdk&version=$JAVA_VERSION&architecture=$JDK_DOWNLOAD_ARCH&latest=available" + info "Downloading JDK $JDK_DOWNLOAD_DISTRIB $JAVA_VERSION to $jdk_cache_dir. It may take a while..." + download_and_unpack "$download_url" "$jdk_cache_dir" "$JDK_DOWNLOAD_FILE_TYPE" + if [ "tar.gz" == "$JDK_DOWNLOAD_FILE_TYPE" ]; then + pushd "$jdk_cache_dir" > /dev/null 2>&1 + local nested_dir + nested_dir=$(find "." -mindepth 1 -maxdepth 1 -type d | head -n 1 | cut -c 3-) + popd > /dev/null 2>&1 + temp_dir=$(mktemp -d) + if [ "$JDK_DOWNLOAD_OS" = "mac" ]; then + nested_dir+="/Contents/Home" + fi + mv "$jdk_cache_dir"/"$nested_dir"/* "$temp_dir" + mv "$temp_dir"/* "$jdk_cache_dir" + fi + fi + DOWNLOAD_JDK_DIR=$jdk_cache_dir +} + +####################################### +# Computes Java command according version, distrib, os and arch, implying optional +# JDK download +# Arguments: +# Global vars: +# JDK_DOWNLOAD_OS +# JDK_DOWNLOAD_LIBC_TYPE +# JDK_DOWNLOAD_ARCH +# JAVA_VERSION +# JAVA_HOME +# DEFAULT_JAVA_VERSION (read) +# JEKA_JDK_HOME +# IS_VERBOSE (read) +# BASE_DIR (read) +# JAVA_CMD +# Outputs: +# result in BASE_DIR global args +####################################### +compute_JAVA_CMD() { + if [ "$JAVA_CMD" != "" ]; then + return 0; + fi + + # OS specific support. $var _must_ be set to either true or false. + case "$(uname -s)" in + Linux*) + JDK_DOWNLOAD_OS="linux" + if [ -f /etc/alpine-release ]; then + JDK_DOWNLOAD_OS=alpine-linux + fi + ;; + Darwin*) + JDK_DOWNLOAD_OS="mac" + JDK_DOWNLOAD_LIBC_TYPE="libc"; # necessary to download proper JDK + ;; + esac + + case "$(uname -m)" in + i?86) + JDK_DOWNLOAD_ARCH="x32";; + x86_64|amd64) + JDK_DOWNLOAD_ARCH="x64";; + aarch64) + JDK_DOWNLOAD_ARCH="aarch64";; + armv7l) + JDK_DOWNLOAD_ARCH="arm";; + ppc64le) + JDK_DOWNLOAD_ARCH="ppc64le";; + s390x) + JDK_DOWNLOAD_ARCH="s390x";; + arm64) + JDK_DOWNLOAD_ARCH="arm64" + ;; + *) + JDK_DOWNLOAD_ARCH="" + ;; + esac + + # Determines JAVA_HOME + JAVA_VERSION=$(get_java_version_from_props "$BASE_DIR") + + if [ -n "$JEKA_JDK_HOME" ]; then # We can enforce usage of a specific JDK by setting JEKA_JDK_HOME env var + JAVA_HOME="$JEKA_JDK_HOME" + + elif [ -n "$JAVA_VERSION" ] || [ -z "$JAVA_HOME" ]; then # if a Java version is specified in then use one of the JeKa managed JDK + if [ -z "$JAVA_VERSION" ]; then + JAVA_VERSION=$"$DEFAULT_JAVA_VERSION" + if [ -n "$IS_VERBOSE" ]; then + info "No JAVA_HOME defined and no jeka.java.version defined. Use Java $DEFAULT_JAVA_VERSION." + fi + fi + jdkPath=$(get_jdk_home_from_props "$BASE_DIR" "$JAVA_VERSION") + debug "JDK HOME $JAVA_VERSION from env or props : $jdkPath " + if [ -z "$jdkPath" ]; then + get_or_download_jdk "$JAVA_VERSION" + JAVA_HOME="$DOWNLOAD_JDK_DIR" + fi + fi + + # Determines JAVA_CMD to use according JAVA_HOME + if [ -z "$JAVA_CMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVA_CMD="$JAVA_HOME/jre/sh/java" + else + JAVA_CMD="$JAVA_HOME/bin/java" + fi + else + JAVA_CMD="$(which java)" + fi + fi + + if [ ! -x "$JAVA_CMD" ] ; then + msg "Error: JAVA_HOME is not defined correctly (valued to $JAVA_HOME )." + msg " We cannot execute $JAVA_CMD" >&2 + msg " You can specify which JDK to use by setting JEKA_JDK environment variable." + exit 1 + fi + +} + +####################################### +# Execute java program if requested. +# Program execution is requested when '-p' is present in cmd line args. +# The args following '-p' are to be passed to program args. +# First, Jeka try to find an executable bin or jar, in 'jeka-output' +# If there is none, jeka launches build and retry. +# +# Global Vars: +# BASE_DIR (read) +# JEKA_DIST_DIR (write) +# RETRY_AFTER_BUILD (read/write) +# INTERPOLATED_ARGS (read) +# PROGRAM_OPTION_INDEX (read) +# Outputs: +# None. Exit anyway if program launch has been requested +####################################### +execute_program_if_requested() { + if [[ $PROGRAM_OPTION_INDEX == -1 ]]; then + return 0 + fi + local -i from_arg_index + from_arg_index=$((PROGRAM_OPTION_INDEX+1)) + local program_args=("${INTERPOLATED_ARGS[@]:$from_arg_index}") + execute_and_exit_native_if_present "${program_args[@]}" + execute_and_exit_java_if_present "${program_args[@]}" + + if [[ "$RETRY_AFTER_BUILD" == "true" ]]; then + msg "Cannot find a native or jar executable in $BASE_DIR/jeka-output" + exit 1 + fi + + ## If we are here, this means that no native or jar has been found -> build + debug "Launch a build to generate executable" + compute_JAVA_CMD + compute_JEKA_DIST_DIR "$BASE_DIR" + compute_JEKA_CLASSPATH "$JEKA_DIST_DIR" + local -a heading_args=("${INTERPOLATED_ARGS[@]::$PROGRAM_OPTION_INDEX}") + + local build_cmd + build_cmd=$(get_prop_value_from_base_dir "$BASE_DIR" "jeka.program.build") + if [[ -z "$build_cmd" ]]; then + if [ -d "$BASE_DIR/src" ]; then + build_cmd="project: pack -Djeka.skip.tests=true --stderr" + else + build_cmd="base: pack -Djeka.skip.tests=true --stderr" + fi + fi + + # shellcheck disable=SC2206 + local -a build_args=($build_cmd) + # shellcheck disable=SC2145 + info "Launch build with command : ${heading_args[@]} ${build_args[@]}" + "$JAVA_CMD" "${JEKA_OPTS[@]}" "-Djeka.current.basedir=$BASE_DIR" -cp "$JEKA_CLASSPATH" "dev.jeka.core.tool.Main" "${heading_args[@]}" "${build_args[@]}" + RETRY_AFTER_BUILD="true" + execute_program_if_requested + +} + +####################################### +# Gets the index of '-p' or '--program' in the given array +# Returns -1, if no such element found +# Globals: +# none +# Arguments: +# $1 : the arrays where we are searching element in +# $2 : the array providing elements to search +# Outputs: +# Write to stdout +####################################### +compute_PROGRAM_OPTION_INDEX() { + local -i index=0 + local -a items=("$@") + for item in "${items[@]}"; do + if [ "$item" == "-p" ] || [ "$item" == "--program" ]; then + PROGRAM_OPTION_INDEX="$index" + return 0 + fi + ((index += 1)) + + done + PROGRAM_OPTION_INDEX=-1 +} + +###################################### +# Execute and exit native program if present +# +# Global Vars: +# BASE_DIR (read) +# Arguments: +# $1 : args array to pass to native program +# Outputs: +# None. Exit anyway if program found +####################################### +execute_and_exit_native_if_present() { + local exe_file; + for file in "$BASE_DIR"/jeka-output/*; do + if [ -f "$file" ] && [ -x "$file" ] && [[ "$file" != *".jar" ]]; then + exe_file="$file" + break + fi + done + if [ "$exe_file" == "" ]; then + debug "No native exe file found in $BASE_DIR/jeka-output/*" + return 0 + fi + exec "$exe_file" "$@" + exit $? +} + +###################################### +# Execute and exit java program if present +# +# Global Vars: +# BASE_DIR (read) +# Arguments: +# $1 : args array to pass to native program +# Outputs: +# None. Exit anyway if program found +####################################### +execute_and_exit_java_if_present() { + local jar_file; + for file in "$BASE_DIR"/jeka-output/*; do + if [ -f "$file" ] && [[ "$file" == *".jar" ]]; then + jar_file="$file" + break + fi + done + if [ "$jar_file" == "" ]; then + debug "No Jar file found in $BASE_DIR/jeka-output/*" + return 0 + fi + compute_JAVA_CMD + local -a sysProp_params + filter_in_sysProp "$@" + sysProp_params=("${returned[@]}") + local -a regular_params + filter_out_sysProp "$@" + regular_params=("${returned[@]}") + exec "$JAVA_CMD" "${sysProp_params[@]}" -jar "$jar_file" "${regular_params[@]}" + exit $? +} + +###################################### +# Filter array keeping only items that match '-Dxxx=yyy' (sys prop) +# +# Global Vars: +# returned (write) +# Arguments: +# $@ : arrays to filter +# Outputs: +# The filtered array is written to in the 'returned' global var +####################################### +filter_in_sysProp() { + local arr=("$@") + returned=() + for i in "${arr[@]}"; do + if [[ $i == -D* ]] && [[ $i == *"="* ]]; then + returned+=("$i") + fi + done +} + +###################################### +# Filter array removing items that match '-Dxxx=yyy' (sys prop) +# +# Global Vars: +# returned (write) +# Arguments: +# $@ : arrays to filter +# Outputs: +# The filtered array is written to in the 'returned' global var +####################################### +filter_out_sysProp() { + local arr=("$@") + returned=() + for i in "${arr[@]}"; do + if [[ $i != -D* ]] || [[ $i != *"="* ]]; then + returned+=("$i") + fi + done +} + +############################################################## +# Script starts here +############################################################## + +compute_VERBOSE_QUIET +JEKA_USER_HOME=$(get_jeka_user_home) +GLOBAL_PROP_FILE="$JEKA_USER_HOME/global.properties" + +compute_INTERPOLATED_ARGS "${CMD_LINE_ARGS[@]}" +compute_PROGRAM_OPTION_INDEX "${INTERPOLATED_ARGS[@]}" +compute_BASE_DIR "${INTERPOLATED_ARGS[@]}" + +execute_program_if_requested + +compute_JAVA_CMD + +## When debugging we don't want to execute Jeka +if [ -z "$DRY_RUN" ]; then + compute_JEKA_DIST_DIR "$BASE_DIR" + compute_JEKA_CLASSPATH "$JEKA_DIST_DIR" + + exec "$JAVA_CMD" "${JEKA_OPTS[@]}" "-Djeka.current.basedir=$BASE_DIR" -cp "$JEKA_CLASSPATH" "dev.jeka.core.tool.Main" "$@" +fi diff --git a/jeka.properties b/jeka.properties index 261393a..d0b366e 100644 --- a/jeka.properties +++ b/jeka.properties @@ -1,5 +1,4 @@ -#jeka.version=0.11.0-alpha.3 +jeka.version=0.11.0-alpha.5 jeka.java.version=8 -jeka.java.distrib=corretto -jeka.cmd.build=base: pack \ No newline at end of file + diff --git a/jeka.ps1 b/jeka.ps1 new file mode 100644 index 0000000..7b07ec1 --- /dev/null +++ b/jeka.ps1 @@ -0,0 +1,619 @@ +#Requires -Version 5 + +# +# Copyright 2014-2024 the original author or authors. +# +# Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# +# Author : Jérôme Angibaud +# +# Execute JeKa by Downloading requiered potentialy missing dependencies (JDK, JeKa version) +# The passed arguments are interpolated then passed to JeKA engine. +# + +function MessageInfo { + param([string]$msg) + if ($global:QuietFlag -ne $true) { + [Console]::Error.WriteLine($msg) + } + +} + +function MessageVerbose { + param([string]$msg) + + if (($VerbosePreference -eq "Continue") -and ($global:QuietFlag -ne $true)) { + [Console]::Error.WriteLine($msg) + } + +} + +function Exit-Error { + param([string]$msg) + + Write-Host $msg -ForegroundColor Red + exit 1 +} + +function Get-JekaUserHome { + if ([string]::IsNullOrEmpty($env:JEKA_USER_HOME)) { + return "$env:USERPROFILE\.jeka" + } + else { + return $env:JEKA_USER_HOME + } +} + +function Get-CacheDir([string]$jekaUserHome) { + if ([string]::IsNullOrEmpty($env:JEKA_CACHE_DIR)) { + return $jekaUserHome + "\cache" + } + else { + return $env:JEKA_CACHE_DIR + } +} + +class BaseDirResolver { + [string]$url + [string]$cacheDir + [bool]$updateFlag + + BaseDirResolver([string]$url, [string]$cacheDir, [bool]$updateFlag) { + $this.url = $url + $this.cacheDir = $cacheDir + $this.updateFlag = $updateFlag + } + + [string] GetPath() { + if ($this.url -eq '') { + return $PWD.Path + } + if ($this.IsGitRemote() -eq $false) { + return $this.url + } + $path = $this.cacheDir + "\git\" + $this.FolderName() + if ([System.IO.Directory]::Exists($path)) { + if ($this.updateFlag) { + Remove-Item -Path $path -Recurse -Force + $this.GitClone($path) + } + } else { + $this.GitClone($path) + } + return $path + } + + hidden [void] GitClone([string]$path) { + $branchArgs = $this.SubstringAfterHash() + if ($branchArgs -ne '') { + $branchArgs = "--branch " + $branchArgs + } + $repoUrl = $this.SubstringBeforeHash() + MessageInfo "Git clone $repoUrl" + $gitCmd = "git clone --quiet -c advice.detachedHead=false --depth 1 $branchArgs $repoUrl $path" + Invoke-Expression -Command $gitCmd + } + + hidden [bool] IsGitRemote() { + $gitUrlRegex = '(https?://*)|^(ssh://*)|^(git://*)|^(git@[^:]+:*)$' + $myUrl = $this.url + if ($myUrl -match $gitUrlRegex) { + return $true; + } else { + return $false; + } + } + + hidden [string] SubstringBeforeHash() { + return $this.url.Split('#')[0] + } + + hidden [string] SubstringAfterHash() { + return $this.url.Split('#')[1] + } + + hidden [string] FolderName() { + $protocols = @('https://', 'ssh://', 'git://', 'git@') + $trimmedUrl = $this.url + foreach ($protocol in $protocols) { + $trimmedUrl = $trimmedUrl -replace [regex]::Escape($protocol), '' + } + # replace '/' by '_' + $folderName = $trimmedUrl -replace '/', '_' + return $folderName + } + +} + +class CmdLineArgs { + [Array]$args + + CmdLineArgs([Array]$arguments) { + $this.args = $arguments + } + + [string] GetSystemProperty([string]$propName) { + $prefix = "-D$propName=" + foreach ($arg in $this.args) { + if ($arg -eq $null) { + continue + } + if ( $arg.StartsWith($prefix) ) { + return $arg.Replace($prefix, "") + } + } + return $null + } + + [int] GetIndexOfFirstOf([Array]$candidates) { + foreach ($arg in $candidates) { + $index = $this.args.IndexOf($arg) + if ($index -ne -1) { + return $index + } + } + return -1 + } + + [string] GetRemoteBaseDirArg() { + $remoteArgs= @("-r", "--remote", "-ru", "-ur") + $remoteIndex= $this.GetIndexOfFirstOf($remoteArgs) + if ($remoteIndex -eq -1) { + return $null + } + return $this.args[$remoteIndex + 1] + } + + [array] GetProgramArgs() { + $index = $this.GetProgramArgIndex() + 1 + return $this.args[$index..$this.args.Length] + } + + [array] GetPriorProgramArgs() { + $index = $this.GetProgramArgIndex() - 1 + if ($index -lt 0) { + return @() + } + return $this.args[0..$index] + } + + [bool] IsUpdateFlagPresent() { + $remoteArgs= @("-ru", "-ur", "-u", "--remote-update") + $remoteIndex= $this.GetIndexOfFirstOf($remoteArgs) + return ($remoteIndex -ne -1) + } + + [bool] IsQuietFlagPresent() { + $remoteArgs= @("-q", "--quiet") + $remoteIndex= $this.GetIndexOfFirstOf($remoteArgs) + return ($remoteIndex -ne -1) + } + + [bool] IsVerboseFlagPresent() { + $remoteArgs= @("-v", "--verbose", "--debug") + $remoteIndex= $this.GetIndexOfFirstOf($remoteArgs) + return ($remoteIndex -ne -1) + } + + [bool] IsProgramFlagPresent() { + $remoteIndex= $this.GetProgramArgIndex() + return ($remoteIndex -ne -1) + } + + [array] FilterOutSysProp() { + $result = @() + foreach ($item in $this.args) { + if (!($item.StartsWith("-D") -AND ($item.Contains("="))) ) { + $result += $item + } + } + return $result + } + + [array] FilterInSysProp() { + $result = @() + foreach ($item in $this.args) { + if ($item.StartsWith("-D") -AND ($item.Contains("=")) ) { + $result += $item + } + } + return $result + } + + hidden [Int16] GetProgramArgIndex() { + $remoteArgs= @("-p", "--program") + return $this.GetIndexOfFirstOf($remoteArgs) + } + +} + +class Props { + [CmdLineArgs]$cmdLineArgs + [string]$baseDir + [string]$globalPropFile + + Props([CmdLineArgs]$cmdLineArgs, [string]$baseDir, [string]$globalPropFile ) { + $this.cmdLineArgs = $cmdLineArgs + $this.baseDir = $baseDir + $this.globalPropFile = $globalPropFile + } + + [string] GetValue([string]$propName) { + $cmdArgsValue = $this.cmdLineArgs.GetSystemProperty($propName) + if ($cmdArgsValue -ne '') { + return $cmdArgsValue + } + $envValue = [Environment]::GetEnvironmentVariable($propName) + if ($envValue) { + return $envValue + } + + $jekaPropertyFilePath = $this.baseDir + '\jeka.properties' + + $value = [Props]::GetValueFromFile($jekaPropertyFilePath, $propName) + if ('' -eq $value) { + $parentDir = $this.baseDir + '\..' + $parentJekaPropsFile = $parentDir + '\jeka.properties' + if (Test-Path $parentJekaPropsFile) { + $value = [Props]::GetValueFromFile($parentJekaPropsFile, $propName) + } else { + $value = [Props]::GetValueFromFile($this.globalPropFile, $propName) + } + } + return $value + } + + [string] GetValueOrDefault([string]$propName, [string]$defaultValue) { + $value = $this.GetValue($propName) + if ($value -eq '') { + return $defaultValue + } else { + return $value + } + } + + [CmdLineArgs] InterpolatedCmdLine() { + $result = @() + foreach ($arg in $this.cmdLineArgs.args) { + if ($arg -like "::*") { + $propKey= ( "jeka.cmd." + $arg.Substring(2) ) + $propValue= $this.GetValue($propKey) + if ('' -ne $propValue) { + $valueArray= [Props]::ParseCommandLine($propValue) + $result += $valueArray + } else { + $result += $arg + } + } else { + $result += $arg + } + } + return [CmdLineArgs]::new($result) + } + + static [array] ParseCommandLine([string]$cmdLine) { + $pattern = '(""[^""]*""|[^ ]*)' + $regex = New-Object Text.RegularExpressions.Regex $pattern + return $regex.Matches($cmdLine) | ForEach-Object { $_.Value.Trim('"') } | Where-Object { $_ -ne "" } + } + + static [string] GetValueFromFile([string]$filePath, [string]$propertyName) { + if (! [System.IO.File]::Exists($filePath)) { + return $null + } + $data = [System.IO.File]::ReadAllLines($filePath) + foreach ($line in $data) { + if ($line -match "^$propertyName=") { + $keyLength = $propertyName.Length + 1 + $value = $line.Substring($keyLength) + return $value + } + } + return $null + } + +} + +class Jdks { + [Props]$Props + [string]$CacheDir + + Jdks([Props]$props, [string]$cacheDir) { + $this.Props = $props + $this.CacheDir = $cacheDir + } + + [string] GetJavaCmd() { + $javaHome = $this.JavaHome() + $javaExe = $this.JavaExe($javaHome) + return $javaExe + } + + hidden Install([string]$distrib, [string]$version, [string]$targetDir) { + MessageInfo "Downloading JDK $distrib $version. It may take a while..." + $jdkurl="https://api.foojay.io/disco/v3.0/directuris?distro=$distrib&javafx_bundled=false&libc_type=c_std_lib&archive_type=zip&operating_system=windows&package_type=jdk&version=$version&architecture=x64&latest=available" + $zipExtractor = [ZipExtractor]::new($jdkurl, $targetDir) + $zipExtractor.ExtractRootContent() + } + + hidden [string] CachedJdkDir([string]$version, [string]$distrib) { + return $this.CacheDir + "\jdks\" + $distrib + "-" + $version + } + + hidden [string] JavaExe([string]$javaHome) { + return $javaHome + "\bin\java.exe" + } + + hidden [string] JavaHome() { + if ($Env:JEKA_JAVA_HOME -ne $null) { + return $Env:JEKA_JAVA_HOME + } + $version = ($this.Props.GetValueOrDefault("jeka.java.version", "21")) + $distib = ($this.Props.GetValueOrDefault("jeka.java.distrib", "temurin")) + $cachedJdkDir = $this.CachedJdkDir($version, $distib) + $javaExeFile = $this.JavaExe($cachedJdkDir) + if (! [System.IO.File]::Exists($javaExeFile)) { + $this.Install($distib, $version, $cachedJdkDir) + } + return $cachedJdkDir + } + +} + +class JekaDistrib { + [Props]$props + [String]$cacheDir + + JekaDistrib([Props]$props, [string]$cacheDir) { + $this.props = $props + $this.cacheDir = $cacheDir + } + + [string] GetBinDir() { + $specificLocation = $this.props.GetValue("jeka.distrib.location") + if ($specificLocation -ne '') { + return $specificLocation + } + $jekaVersion = $this.props.GetValue("jeka.version") + + # If version not specified, use jeka jar present in running distrib + if ($jekaVersion -eq '') { + $dir = $PSScriptRoot + $jarFile = $dir + "\dev.jeka.jeka-core.jar" + if (! [System.IO.File]::Exists($jarFile)) { + Write-Host "No Jeka jar file found at $jarFile" -ForegroundColor Red + Write-Host "This is due that neither jeka.distrib.location nor jeka.version are specified in properties, " -ForegroundColor Red + Write-Host "and you are probably invoking local 'jeka.ps1'" -ForegroundColor Red + Write-Host "Specify one the mentionned above properties or invoke 'jeka' if JeKa is installed on host machine." -ForegroundColor Red + exit 1 + } + return $dir + } + + $distDir = $this.cacheDir + "\distributions\" + $jekaVersion + if ( [System.IO.Directory]::Exists($distDir)) { + return "$distDir\bin" + } + + $distRepo = $this.props.GetValueOrDefault("jeka.distrib.repo", "https://repo.maven.apache.org/maven2") + $url = "$distRepo/dev/jeka/jeka-core/$jekaVersion/jeka-core-$jekaVersion-distrib.zip" + $zipExtractor = [ZipExtractor]::new($url, $distDir) + $zipExtractor.Extract() + return "$distDir\bin" + } + + [string] GetJar() { + return $this.GetBinDir() + "\dev.jeka.jeka-core.jar" + } + +} + +class ZipExtractor { + [string]$url + [string]$dir + + ZipExtractor([string]$url, [string]$dir) { + $this.url = $url + $this.dir = $dir + } + + ExtractRootContent() { + $zipFile = $this.Download() + $tempDir = [System.IO.Path]::GetTempFileName() + Remove-Item -Path $tempDir + Expand-Archive -Path $zipFile -DestinationPath $tempDir -Force + $subDirs = Get-ChildItem -Path $tempDir -Directory + $root = $tempDir + "\" + $subDirs[0] + Move-Item -Path $root -Destination $this.dir -Force + Remove-Item -Path $zipFile + Remove-Item -Path $tempDir -Recurse + } + + Extract() { + $zipFile = $this.Download() + Expand-Archive -Path $zipFile -DestinationPath $this.dir -Force + Remove-Item -Path $zipFile + } + + hidden [string] Download() { + $downloadFile = [System.IO.Path]::GetTempFileName() + ".zip" + $webClient = New-Object System.Net.WebClient + $webClient.DownloadFile($this.url, $downloadFile) + $webClient.Dispose() + return $downloadFile + } +} + +class ProgramExecutor { + [string]$folder + [array]$cmdLineArgs + + ProgramExecutor([string]$folder, [array]$cmdLineArgs) { + $this.folder = $folder + $this.cmdLineArgs = $cmdLineArgs + } + + Exec([string]$javaCmd, [string]$progFile) { + if ($progFile.EndsWith('.exe')) { + & "$progFile" $this.cmdLineArgs + } else { + & "$javaCmd" -jar "$progFile" $this.cmdLineArgs + } + } + + [string] FindProg() { + $dir = $this.folder + $exist = [System.IO.Directory]::Exists("$dir") + if (-not (Test-Path $dir)) { + return $null + } + $exeFile = $this.findFile(".exe") + if ($exeFile -ne '') { + return $exeFile + } + return $this.findFile(".jar") + } + + hidden [string] findFile([string]$extension) { + $files = Get-ChildItem -Path $this.folder -Filter "*$extension" + if ($files) { + $firstFile = $files | Select-Object -First 1 + return $firstFile.FullName + } + return $null + } +} + +function ExecJekaEngine { + param( + [string]$baseDir, + [string]$cacheDir, + [Props]$props, + [string]$javaCmd, + [array]$cmdLineArgs, + [Parameter(Mandatory=$false)][bool]$stderr = $false + ) + + $jekaDistrib = [JekaDistrib]::new($props, $cacheDir) + $jekaJar = $jekaDistrib.GetJar() + $classpath = "$baseDir\jeka-boot\*;$jekaJar" + $jekaOpts = $Env:JEKA_OPTS + $baseDirProp = "-Djeka.current.basedir=$baseDir" + & "$javaCmd" $jekaOpts "$baseDirProp" -cp "$classpath" "dev.jeka.core.tool.Main" $cmdLineArgs +} + +function ExecProg { + param( + [string]$javaCmd, + [string]$progFile, + [array]$cmdLineArgs + ) + + $argLine = $cmdLineArgs -join ' ' + if ($progFile.EndsWith('.exe')) { + Write-Verbose "Run native program $progFile with args $argLine" + & "$progFile" $cmdLineArgs + } else { + $cmdLineArgs = [CmdLineArgs]::new($cmdLineArgs) + $sypPropArgs = $cmdLineArgs.FilterInSysProp() + $sanitizedProgArgs = $cmdLineArgs.FilterOutSysProp() + Write-Verbose "Run Java program $progFile with args $argLine" + & "$javaCmd" -jar "$sypPropArgs" "$progFile" $sanitizedProgArgs + } +} + +function Main { + param( + [array]$arguments + ) + + #$argLine = $arguments -join '|' + #MessageInfo "Raw arguments |$argLine|" + $jekaUserHome = Get-JekaUserHome + $cacheDir = Get-CacheDir($jekaUserHome) + $globalPropFile = $jekaUserHome + "\global.properties" + + # Get interpolated cmdLine, while ignoring Base dir + $rawCmdLineArgs = [CmdLineArgs]::new($arguments) + if ($rawCmdLineArgs.IsQuietFlagPresent()) { + $global:QuietFlag = $true + } + $rawProps = [Props]::new($rawCmdLineArgs, $PWD.Path, $globalPropFile) + $cmdLineArgs = $rawProps.InterpolatedCmdLine() + + # Resolve basedir and interpolate cmdLine according props declared in base dir + $remoteArg = $cmdLineArgs.GetRemoteBaseDirArg() + $updateArg = $cmdLineArgs.IsUpdateFlagPresent() + $baseDirResolver = [BaseDirResolver]::new($remoteArg, $cacheDir, $updateArg) + $baseDir = $baseDirResolver.GetPath() + $props = [Props]::new($cmdLineArgs, $baseDir, $globalPropFile) + $cmdLineArgs = $props.InterpolatedCmdLine() + $joinedArgs = $cmdLineArgs.args -join " " + if ($cmdLineArgs.IsVerboseFlagPresent()) { + $VerbosePreference = 'Continue' + } + MessageVerbose "Interpolated cmd line : $joinedArgs" + + # Compute Java command + $jdks = [Jdks]::new($props, $cacheDir) + $javaCmd = $jdks.GetJavaCmd() + + # -p option present : try to execute program directly, bypassing jeka engine + if ($cmdLineArgs.IsProgramFlagPresent()) { + $progArgs = $cmdLineArgs.GetProgramArgs() # arguments metionned after '-p' + $progDir = $baseDir + "\jeka-output" + $prog = [ProgramExecutor]::new($progDir, $progArgs) + $progFile = $prog.FindProg() + if ($progFile -ne '') { + ExecProg -javaCmd $javaCmd -progFile $progFile -cmdLineArgs $progArgs + exit $LASTEXITCODE + } + + # No executable or Jar found : launch a build + $buildCmd = $props.GetValue("jeka.program.build") + MessageInfo "jeka.program.build=$buildCmd" + if (!$buildCmd) { + $srcDir = $baseDir + "\src" + if ([System.IO.Directory]::Exists($srcDir)) { + $buildCmd = "project: pack -Djeka.skip.tests=true --stderr" + } else { + $buildCmd = "base: pack -Djeka.skip.tests=true --stderr" + } + } + $buildArgs = [Props]::ParseCommandLine($buildCmd) + $leadingArgs = $cmdLineArgs.GetPriorProgramArgs() + $effectiveArgs = $leadingArgs + $buildArgs + MessageInfo "Building with command : $effectiveArgs" + ExecJekaEngine -baseDir $baseDir -cacheDir $cacheDir -props $props -javaCmd $javaCmd -cmdLineArgs $effectiveArgs + if ($LASTEXITCODE -ne 0) { + Exit-Error "Build exited with error code $LASTEXITCODE. Cannot execute program" + } + $progFile = $prog.FindProg() + if ($progFile -eq '') { + Exit-Error "No program found to be executed in $progDir" + } + ExecProg -javaCmd $javaCmd -progFile $progFile -cmdLineArgs $progArgs + exit $LASTEXITCODE + + # Execute Jeke engine + } else { + ExecJekaEngine -javaCmd $javaCmd -baseDir $baseDir -cacheDir $cacheDir -props $props -cmdLineArgs $cmdLineArgs.args + exit $LASTEXITCODE + } + +} + +$ErrorActionPreference = "Stop" +Main -arguments $args \ No newline at end of file