Last active
February 2, 2020 01:19
-
-
Save shoogle/3c61f7fcd7a086947034ca90167d857c to your computer and use it in GitHub Desktop.
Cross-platform script to build MuseScore on Windows, Mac or Linux
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
source "path_functions.sh" # Bash-Essential-Functions: FilePaths module (BEF-FP) | |
# Cross-platform symlink function. | |
function symlink() { | |
if [[ "$1" == "-s" ]]; then | |
shift | |
fi | |
local target="$1" name="$2" | |
if [[ "${OSTYPE}" == "msys" ]]; then | |
# Windows needs to be told if it's a directory or not. Infer that. | |
# Also: note that we convert `/` to `\`. In this case it's necessary. | |
local args="" | |
if [[ -d "${target}" ]]; then | |
args="/J" | |
fi | |
cmd >/dev/null <<EOF | |
mklink ${args} "${name}" "${target//\//\\}" | |
EOF | |
else | |
ln -s "${target}" "${name}" | |
fi | |
} | |
function safe_symlink() { | |
local -r target="$1" name="$2" | |
if [[ -e "${name}" ]] && [[ ! -L "${name}" ]]; then | |
echo >&2 "Error: File '${name}' exists and is not a symlink. Will not overwrite." | |
exit 1 | |
fi | |
rm -f "${name}" | |
symlink -s "${target}" "${name}" | |
} | |
# cross-platform copy function. | |
# create copy-on-write clones if possible, otherwise make an ordinary copy | |
function cp() { | |
if [[ "${OSTYPE}" == darwin* ]]; then | |
command cp -c "$@" || command cp "$@" | |
else | |
command cp --reflink=auto "$@" | |
fi | |
} | |
# function make() { | |
# local target="$1" | |
# if [[ "${PLATFORM}" == "macOS" ]]; then | |
# [[ "${target}" ]] || target="ALL_BUILD" | |
# xcodebuild -jobs ${CPUS} -project mscore.xcodeproj -configuration Debug -target "${target}" | |
# else | |
# local -r make="$(which make 2>/dev/null || which mingw32-make 2>/dev/null || echo "make")" | |
# command "${make}" -j ${CPUS} "$@" | |
# fi | |
# } | |
function cmake_build() { | |
# CMAKE's build tool mode simply calls the native build tool that the | |
# project was configured for. It translates command line options for us. | |
local cmake_options=("$@") native_options=() | |
if [[ "${PLATFORM}" == "Windows" ]] || [[ "${PLATFORM}" == "macOS" ]]; then | |
# users on these platforms can easily upgrade CMAKE to the latest version | |
cmake_options+=(-j ${CPUS}) # requires cmake version >= 3.12 | |
else | |
# users on these platforms are stuck with their distribution's CMAKE | |
native_options+=(-j ${CPUS}) # native tool must accept -j option | |
fi | |
cmake --build . "${cmake_options[@]}" -- "${native_options[@]}" | |
} | |
function set_variables() { | |
QTDIR="${QTDIR//\\/\/}" # change Windows-style path to Unix-style | |
CPUS="$( getconf _NPROCESSORS_ONLN 2>/dev/null \ | |
|| getconf NPROCESSORS_ONLN 2>/dev/null \ | |
|| echo ${NUMBER_OF_PROCESSORS} )" | |
(($CPUS > 0)) || CPUS=1 | |
bef_fp__setvar_basename "${QTDIR}" | |
COMPILER="${basename}" | |
bef_fp__setvar_dirname "${QTDIR}" && bef_fp__setvar_basename "${dirname}" | |
QT_VERSION="${basename}" | |
GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" | |
case "${OSTYPE}" in | |
msys ) PLATFORM="Windows" ;; | |
darwin* ) PLATFORM="macOS" ;; | |
* ) PLATFORM="other" ;; | |
esac | |
if [[ "${PLATFORM}" == "macOS" ]]; then | |
# PATH set in .bash_profile will not be set in Qt Creator. Set it here: | |
export PATH="/usr/local/bin:${PATH}" # homebrew | |
fi | |
case "${COMPILER}" in | |
msvc* ) | |
GENERATOR_FAMILY="MSVC" | |
GENERATOR="Visual Studio 16 2019" | |
BUILD_64="OFF" | |
if [[ "${COMPILER}" == msvc*_64 ]]; then | |
# GENERATOR="${GENERATOR} Win64" | |
BUILD_64="ON" | |
fi | |
;; | |
mingw* ) | |
GENERATOR_FAMILY="MINGW" | |
GENERATOR="MinGW Makefiles" | |
BUILD_64="OFF" | |
if [[ "${COMPILER}" == mingw*_64 ]]; then | |
# GENERATOR="${GENERATOR} Win64" | |
BUILD_64="ON" | |
fi | |
;; | |
esac | |
} | |
function set_build_dir() { | |
BUILD_DIR="builds/${GIT_BRANCH}/${OSTYPE}-${HOSTTYPE}/Qt${QT_VERSION}-${COMPILER}" | |
} | |
function link_build_dir() { | |
local invpath | |
mkdir -p "${BUILD_DIR}/install" | |
bef_fp__setvar_invpath "${BUILD_DIR}" && SOURCE_DIR="${invpath}" | |
safe_symlink "${BUILD_DIR}" "build.debug" | |
case "${PLATFORM}" in | |
Windows ) INSTALL_LINK="win32install" ;; | |
macOS ) INSTALL_LINK="applebuild" ;; | |
* ) INSTALL_LINK="" ;; | |
esac | |
if [[ "${INSTALL_LINK}" ]]; then | |
safe_symlink "build.debug/install" "${INSTALL_LINK}" | |
fi | |
echo "BUILD_DIR: ${BUILD_DIR} SOURCE_DIR: ${SOURCE_DIR} INSTALL_LINK: ${INSTALL_LINK}" | |
} | |
function configure() { | |
local cmake_options=(-DCMAKE_INSTALL_PREFIX=install -DBUILD_WEBENGINE=OFF -DBUILD_WEBEBENGINE=OFF -DDOWNLOAD_ASSETS=ON) | |
if [[ "${PLATFORM}" == "macOS" ]]; then | |
cmake_options+=(-DCMAKE_BUILD_TYPE=RELEASE) # don't build mtest | |
else | |
cmake_options+=(-DCMAKE_BUILD_TYPE=DEBUG) | |
fi | |
case "${PLATFORM}" in | |
Windows ) | |
cmake_options+=(-G "${GENERATOR}" -DBUILD_64="${BUILD_64}") | |
if [[ "${GENERATOR_FAMILY}" == "MINGW" ]]; then | |
cmake_options+=(-DCMAKE_MAKE_PROGRAM=mingw32-make.exe) | |
fi | |
;; | |
macOS ) cmake_options+=(-G "Xcode") ;; | |
esac | |
cmake "${cmake_options[@]}" "${SOURCE_DIR}" | |
# make lrelease # do this in configure step since we don't need to do it on every build | |
cmake_build --target lrelease | |
} | |
function build() { | |
# make | |
cmake_build | |
} | |
function install() { | |
# make install | |
cmake_build --target install | |
} | |
function clean() { | |
rm -rf "${BUILD_DIR}" | |
} | |
function copy() { | |
# copy the build directory of an existing branch to speed up a clean build | |
local dest_dir branch="$1" | |
dest_dir="${BUILD_DIR}" | |
GIT_BRANCH="${branch}" set_build_dir | |
if [[ ! -d "${BUILD_DIR}" ]]; then | |
echo "$0: error: no suitable build found for branch '${branch}'." >&2 | |
exit 1 | |
fi | |
if [[ -d "${dest_dir}" ]]; then | |
echo "$0: error: build dir not clean for branch '${dest_dir}'." >&2 | |
exit 1 | |
fi | |
echo "$0: copying build directory for branch '${branch}'." >&2 | |
time cp -rp "${BUILD_DIR}" "${dest_dir}" | |
echo "$0: deleting 'CMakeCache.txt' to avoid CMake warnings." >&2 | |
rm "${dest_dir}/CMakeCache.txt" | |
BUILD_DIR="${dest_dir}" | |
} | |
function refresh() { | |
# ensure ignored IDE project files match repository versions | |
local platform | |
case "${PLATFORM}" in | |
Windows ) platform="Windows" ;; | |
macOS ) platform="Mac" ;; | |
* ) platform="Linux" ;; | |
esac | |
local project_files=( | |
bef-filepaths.sh | |
musescore_build.sh | |
musescore_build_windows.bat | |
MuseScore_${platform}.config | |
MuseScore_${platform}.creator | |
MuseScore_${platform}.creator.user | |
MuseScore_${platform}.files | |
MuseScore_${platform}.includes | |
) | |
for file in "${project_files[@]}"; do | |
refresh_file "${file}" | |
done | |
refresh_file exclude .git/info/exclude | |
} | |
function refresh_file() { | |
local -r here="${HOME}/ssrc/MuseScore/musescore_build" | |
local -r stored="${here}/$1" ignored="${2:-$1}" | |
if [[ -e "${stored}" ]]; then | |
if [[ "${stored}" -nt "${ignored}" ]] ; then | |
echo "$0: refreshing file: ${ignored}" >&2 | |
cp -p "${stored}" "${ignored}" | |
elif [[ "${ignored}" -nt "${stored}" ]]; then | |
echo "$0: refreshing file: ${stored} (remember to commit)" >&2 | |
cp -p "${ignored}" "${stored}" | |
fi | |
else | |
echo "$0: file not found: ${stored}" >&2 | |
fi | |
} | |
function parse_args() { | |
local arg param | |
# local get_param='param="${arg:2}" ; [[ "${param}" ]] || { param="$1"; shift ;}' | |
unset arg_clean arg_configure arg_build arg_install # build steps | |
unset arg_copy # other arguments | |
# unset arg_generator arg_build_type # cmake options | |
while (($# > 0)); do | |
arg="$1" | |
shift | |
case "${arg}" in | |
# BUILD STEPS | |
clean ) | |
arg_clean=1 | |
if [[ "$1" == "--copy" ]]; then | |
# copy another branch's build dir to speed up build | |
arg_copy="$2" | |
shift 2 | |
fi | |
;; | |
configure ) arg_configure=1 ;; | |
build ) arg_build=1 ;; | |
install ) arg_install=1 ;; | |
# CMAKE OPTIONS | |
# -G* ) eval "${get_param}" ; arg_generator="${param}" ;; | |
# -D* ) eval "${get_param}" ; arg_generator="${param}" | |
* ) echo "$0: unrecognised option: ${arg}" >&2 ;; | |
esac | |
done | |
} | |
function main() { | |
set -e # exit immediately on error | |
if ((${BASH_VERSION:0:1} < 4)); then | |
echo "$0: Error: Bash version 4 or later required. Please upgrade!" >&2 | |
local mac_help="https://coderwall.com/p/dmuxma/upgrade-bash-on-your-mac-os" | |
echo "macOS users please read <${mac_help}>." >&2 | |
exit 1 | |
fi | |
parse_args "$@" | |
set_variables | |
set_build_dir | |
link_build_dir | |
refresh | |
if [[ "${arg_clean}" ]]; then | |
clean | |
if [[ "${arg_copy}" ]]; then copy "${arg_copy}"; fi | |
fi | |
if [[ "${arg_configure}${arg_build}${arg_install}" ]]; then | |
cd "${BUILD_DIR}" || { echo "$0: error: not yet configured!" >&2 ; exit 1 ;} | |
if [[ "${arg_configure}" ]]; then configure; fi | |
if [[ "${arg_build}" ]]; then build ; fi | |
if [[ "${arg_install}" ]]; then install ; fi | |
fi | |
} | |
printf "%q " "$0" "$@" && echo # display script name and arguments passed by user | |
main "$@" # call main function |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
:: I recommend installing Git via Scoop (https://scoop.sh/) | |
set BASH="%USERPROFILE%\scoop\apps\git-with-openssh\current\bin\bash.exe" | |
:: If you prefer to install Git manually, uncomment next line | |
:: set BASH="C:\Program Files (x86)\Git\bin\bash.exe" | |
%BASH% --login -i -c "./musescore_build.sh %*" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
############# BASH ESSENTIAL FUNCTIONS - FILEPATHS MODULE (BEF-FP) ############# | |
# If copying the contents of this module into your own script be sure to comment | |
# out the next line, otherwise this library's usage message will be displayed. | |
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then case "$1" in -l*|--l*) message=' | |
################################## LICENSE ##################################### | |
# Bash Essential Functions - FilePaths module (BEF-FP) | |
# | |
# The Bash Essential Functions library is licensed under the MIT License (a.k.a. | |
# the "Expat License"), details of which are below. Code contributions are | |
# welcome at <https://github.com/shoogle/bash-essential-functions>. | |
# | |
# MIT License | |
# | |
# Copyright (c) 2017 Peter Jonas | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in all | |
# copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
# SOFTWARE. | |
################################################################################ | |
#' ;; -h*|-h*) message=' | |
#################################### HELP ###################################### | |
# Bash Essential Functions - FilePaths module (BEF-FP) | |
# | |
# Source this file to get access to the functions it contains: | |
# | |
# $ source bef-filepaths.sh [--no-convenience-functions] | |
# | |
# Functions in this module are named `bef_fp__function_name` to avoid polluting | |
# the global namespace. After souring the file you can list the functions with: | |
# | |
# $ declare -F | grep "bef_fp" # `declare -f` includes function definitions! | |
# | |
# BEF modules provide convenience functions that accept data piped from STDIN | |
# and echo the result to STDOUT, which can be saved using command substitution: | |
# | |
# $ myvar="$(pipeline | bef_fp__function_name "${args}")" # convenient! | |
# | |
# However, this is not recommended because command substitution strips trailing | |
# newlines, which, although rarely used, are valid characters in Unix filenames. | |
# Instead, you should consider using the "setvar" functions: | |
# | |
# $ declare funcvar && bef_fp__setvar_funcvar "${arg}" # faster & safer! | |
# | |
# This sets the variable "funcvar" to the output of the first function, without | |
# losing any trailing newline characters. Using the setvar function is faster | |
# than command substitution because it avoids spawning a subshell. Most setvar | |
# functions do not accept piped data because this could spawn a subshell. | |
# | |
# If you want to disable the convenience functions entirely and keep only setvar | |
# functions then source the file with the `--no-convenience-functions` option. | |
################################################################################ | |
#' ;; *) message=" | |
#################################### USAGE ##################################### | |
# Bash Essential Functions - FilePaths module (BEF-FP) | |
# | |
# This script is a Bash function library and should be sourced rather than run. | |
# | |
# Usage: .|source bef-filepaths.sh [--no-convenience-functions] | |
# | |
# However, it may be run with the following options to display info messages: | |
# | |
# Usage: [bash] ./bef-filepaths.sh [-h|--help] [-l|--license] | |
# | |
# Please submit code contributions and bug reports to the upstream repository | |
# at <https://github.com/shoogle/bash-essential-functions>. Thanks! | |
################################################################################ | |
#" ;; esac; IFS=$'\n' read -d '' -n ${#message} -a lines <<<"${message} | |
#" ; for ((i=1;i<${#lines[@]}-2;i++)); do echo "${lines[i]:2}"; done; exit; fi | |
################################################################################ | |
if [[ "$1" != "--no-convenience-functions" ]]; then | |
function bef_fp__basename() { | |
local path basename \ | |
&& bef_fp__setvar_path_from_args_or_stdin "$@" \ | |
&& bef_fp__setvar_basename "${path}" \ | |
&& echo "${basename}" | |
} | |
function bef_fp__dirname() { | |
local path dirname \ | |
&& bef_fp__setvar_path_from_args_or_stdin "$@" \ | |
&& bef_fp__setvar_dirname "${path}" \ | |
&& echo "${dirname}" | |
} | |
# Appends $PWD to $path if $path is relative (does not start with '/'). $path | |
# does not have to exist. Output is not normalized or canonicalized. | |
function bef_fp__absolute_path() { | |
local path abspath \ | |
&& bef_fp__setvar_path_from_args_or_stdin "$@" \ | |
&& bef_fp__setvar_abspath "${path}" \ | |
&& echo "${abspath}" | |
} | |
function bef_fp__normalized_path() { | |
local path normpath \ | |
&& bef_fp__setvar_path_from_args_or_stdin "$@" \ | |
&& bef_fp__setvar_normpath "${path}" \ | |
&& echo "${normpath}" | |
} | |
function bef_fp__inverse_path() { | |
local path invpath \ | |
&& bef_fp__setvar_path_from_args_or_stdin "$@" \ | |
&& bef_fp__setvar_invpath "${path}" \ | |
&& echo "${invpath}" | |
} | |
function bef_fp__canonical_path() { | |
local path canonpath \ | |
&& bef_fp__setvar_path_from_args_or_stdin "$@" \ | |
&& bef_fp__setvar_canonpath "${path}" \ | |
&& echo "${canonpath}" | |
} | |
fi # [[ "$1" != "--no-convenience-functions" ]] | |
# set $path variable in caller shell with input from $1 or STDIN | |
# Usage: local path && bef_fp__setvar_path_from_args_or_stdin "$@" | |
function bef_fp__setvar_path_from_args_or_stdin() { | |
if (($# > 0)); then | |
path="$1" | |
else | |
read -d '' -r path # path from STDIN (path may contain newlines) | |
path="${path%$'\n'}" # strip extra newline added by read -d '' | |
fi | |
} | |
# set $nodes array in caller shell with result of splitting $1 on "/" | |
# Usage: local nodes=() && bef_fp__setvar_nodes_array "${path}" | |
function bef_fp__setvar_nodes_array() { | |
local length=${#1} IFS="/" # split path on "/" | |
# read $path into array. Only read $lenght chars to avoid extra '\n' chars. | |
((length == 0)) || read -d '' -n $length -r -a nodes <<<"$1" | |
} | |
# set $basename variable in caller shell with basename of $1. Avoids subshell. | |
# Usage: local basename && bef_fp__setvar_basename "${path}" | |
function bef_fp__setvar_basename() { | |
local path="$1" | |
if [[ "${path}" =~ ^/+$ ]]; then | |
basename="/" # path consists only of slashes | |
return # '/////' gives '/' | |
fi | |
while [[ "${path}" == *"/" ]]; do | |
path="${path%/}" # strip trailing slash(es): 'foo/bar//' goes to 'foo/bar' | |
done | |
# return everything after final slash, or everything if there are no slashes | |
basename="${path##*/}" # 'foo/bar' or 'bar' gives 'bar' | |
} | |
# set $dirname variable in caller function dirname of $1. Avoids subshell. | |
# Usage: local dirname && bef_fp__setvar_dirname "${path}" | |
function bef_fp__setvar_dirname() { | |
local path="$1" | |
if [[ "${path}" =~ ^/*[^/]*/*$ ]]; then # path only has one node: //foo/// | |
if [[ "${path:0:1}" == / ]]; then | |
dirname="/" # '//foo///', '//foo' or '//' gives '/' (shortest abs. path) | |
else | |
dirname="." # 'foo///', 'foo' or '' gives '.' (shortest relative path) | |
fi | |
return | |
fi | |
while [[ "${path}" == */ ]]; do # strip any trailing slash(es) | |
path="${path%/}" # 'foo///bar///' goes to 'foo///bar' | |
done | |
# only keep characters before the final slash, or everything if no slashes | |
path="${path%/*}" # 'foo///bar' goes to 'foo//', 'foo' goes to 'foo' | |
while [[ "${path}" == */ ]]; do | |
path="${path%/}" # strip trailing slash(es): 'foo//' goes to 'foo' | |
done | |
dirname="${path}" | |
} | |
# Prepends $PWD to $path if $path is relative (does not start with '/'). $path | |
# does not have to exist. Output is not normalized or canonicalized. | |
function bef_fp__setvar_abspath() { | |
local path="$1" | |
if [[ "${path:0:1}" == "/" ]]; then | |
abspath="${path}" | |
else | |
abspath="${PWD}/${path}" | |
fi | |
} | |
# Removes unnecessary slashes and resolves '.' and '..' to return the shortest | |
# path equivalent to the one given as input. The path does not have to exist. | |
function bef_fp__setvar_normpath() { | |
local path="$1" basename dirname abs skip=0 | |
[[ "${path:0:1}" == "/" ]] && abs="/" || abs="" # relative or absolute path? | |
while [[ ! "${path}" =~ ^\.?/*$ ]]; do | |
bef_fp__setvar_basename "${path}" | |
bef_fp__setvar_dirname "${path}" | |
if [[ "${basename}" == "." ]]; then | |
: # ignore node | |
elif [[ "${basename}" == ".." ]]; then | |
((skip++)) # skip next node that isn't '.' or '..' or already skipped | |
elif ((skip > 0)); then | |
((skip--)) # skipping this node due to prior '..' | |
else | |
normpath="${basename}/${normpath}" # add node to normpath | |
fi | |
path="${dirname}" # move to next node | |
done | |
if [[ ! "${abs}" ]]; then | |
while ((skip > 0)); do | |
normpath="../${normpath}" | |
((skip--)) | |
done | |
fi | |
if [[ "${normpath}" ]]; then | |
normpath="${abs}${normpath%/}" # strip trailing slash | |
elif [[ "${abs}" ]]; then | |
normpath="/" | |
else | |
normpath="." | |
fi | |
} | |
# Gives location of working directory relative to specified path | |
# The path does not have to exist. | |
function bef_fp__setvar_invpath() { | |
local path="$1" compath="$2" normpath bk=0 fd=0 | |
if [[ ! "${compath}" ]]; then | |
compath="${PWD}" | |
fi | |
bef_fp__setvar_normpath "${path}" | |
path="${normpath}" | |
if [[ "${path:0:1}" == "/" ]]; then | |
invpath="${PWD}" | |
elif [[ "${path}" == "." ]]; then | |
invpath="." | |
else | |
while [[ "${path}" != "." ]]; do | |
bef_fp__setvar_dirname "${path}" | |
bef_fp__setvar_basename "${path}" | |
if [[ "${basename}" == ".." ]]; then | |
((bk++)) | |
else | |
((fd++)) | |
fi | |
path="${dirname}" | |
done | |
while ((bk > 0)); do | |
bef_fp__setvar_basename "${compath}" | |
invpath="${basename}/${invpath}" | |
bef_fp__setvar_dirname "${compath}" | |
compath="${dirname}" | |
((bk--)) | |
done | |
while ((fd > 0)); do | |
invpath="../${invpath}" | |
((fd--)) | |
done | |
invpath="${invpath%/}" # strip trailing slash | |
fi | |
} | |
function bef_fp__setvar_canondirpath() { | |
local dirpath="$1" | |
# `cd` to $dirpath and back again. ${OLDPWD} created automatically by shell. | |
CDPATH="" cd -P "${dirpath}" \ | |
&& canondirpath="${PWD}" \ | |
&& CDPATH="" cd "${OLDPWD}" \ | |
|| echo "${FUNCNAME[0]}: error: unable to access: ${dirpath}" >&2 | |
} | |
# Prints physical path without symbolic links. ${path} must exist on system. | |
function bef_fp__setvar_canonpath() { | |
local path="$1" canonpath canondirpath | |
if [[ -d "${path}" ]]; then | |
bef_fp__setvar_canondirpath "${path}" | |
canonpath="${canondirpath}" | |
elif [[ ! -e "${path}" ]]; then | |
if [[ -L "${path}" ]]; then | |
echo "${FUNCNAME[0]}: error: no target found for symlink: ${path}" >&2 | |
return 2 | |
else | |
echo "${FUNCNAME[0]}: error: file not found: ${path}" >&2 | |
return 1 | |
fi | |
else | |
local basename dirname symlinks=0 max_symlinks=40 | |
while [[ -L "${path}" ]]; do | |
if ((symlinks++ && symlinks > max_symlinks)); then | |
echo "${FUNCNAME[0]}: error: too many symlinks: ${path}" >&2 | |
return 3 | |
fi | |
# Must read the link. Can we avoid using subshell and external binary? | |
canonpath="$(readlink "${path}")" # XXX: Can we avoid this? | |
if [[ "${canonpath:0:1}" == "/" ]]; then | |
path="${canonpath}" | |
else | |
bef_fp__setvar_dirname "${path}" | |
path="${dirname}/${canonpath}" | |
fi | |
done | |
bef_fp__setvar_basename "${path}" | |
bef_fp__setvar_dirname "${path}" | |
bef_fp__setvar_canondirpath "${dirname}" | |
canonpath="${canondirpath}/${basename}" | |
fi | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment