Skip to content

Instantly share code, notes, and snippets.

@tresf
Created May 30, 2024 21:50
Show Gist options
  • Save tresf/0b99944ca5709115cb7895f1059dd9a4 to your computer and use it in GitHub Desktop.
Save tresf/0b99944ca5709115cb7895f1059dd9a4 to your computer and use it in GitHub Desktop.
# Downloads an executable from the provided URL for use in a build system
# and optionally prepends it to the PATH
#
# Assumes:
# - CMAKE_CURRENT_BINARY_DIR/[${_name}]
# - CPACK_CURRENT_BINARY_DIR/[${_name}]
# - Fallback to $ENV{TMPDIR}/[RANDOM]/[${_name}]
# - For verbose, set COMMAND_ECHO to STDOUT in calling script
#
macro(download_binary RESULT_VARIABLE _url _name _prepend_to_path)
if(NOT COMMAND_ECHO OR "${COMMAND_ECHO}" STREQUAL "NONE")
set(_command_echo NONE)
set(_output_quiet OUTPUT_QUIET)
set(_error_quiet ERROR_QUIET)
else()
set(_command_echo "${COMMAND_ECHO}")
set(_output_quiet "")
set(_error_quiet "")
endif()
# Allow special "foo.run --target ./bar" handling for .run files
if("${_url}" MATCHES "\\.run$" OR "${_name}" MATCHES "\\.run$")
set(_${RESULT_VARIABLE}_USES_MAKESELF true)
endif()
# Check if fuse is needed
if("${RESULT_VARIABLE}" MATCHES "\\.AppImage$" OR "${_name}" MATCHES "\\.AppImage$")
message("Checking if FUSE is available to the kernel...")
set(_${RESULT_VARIABLE}_NEEDS_FUSE true)
endif()
# Check if fuse is available
if(_${RESULT_VARIABLE}_NEEDS_FUSE)
# Ensure we only check on first call
if(NOT _FUSE_CHECKED)
execute_process(COMMAND fusermount -V
${_output_quiet}
COMMAND_ECHO ${_command_echo}
RESULT_VARIABLE _status_code)
set(_FUSED_CHECKED TRUE)
if(_status_code EQUAL 0)
set(_FUSE_FOUND true)
message("FUSE is available")
else()
message("FUSE is NOT available, we'll extract the AppImage before using")
endif()
endif()
endif()
# Determine a suitable working directory
if(CMAKE_CURRENT_BINARY_DIR)
# Assume we're called from configure step
set(_working_dir "${CMAKE_CURRENT_BINARY_DIR}")
elseif(CPACK_CURRENT_BINARY_DIR)
# Assume cpack (non-standard variable name, but used throughout)
set(_working_dir "${CPACK_CURRENT_BINARY_DIR}")
else()
# Fallback to somewhere temporary, writable
if($ENV{_tmpdir})
# POSIX
set(_tmpdir "$ENV{_tmpdir}")
elseif($ENV{TEMP})
# Windows
set(_tmpdir "$ENV{TEMP}")
else()
# Linux, shame on you!
find_program(MKTEMP mktemp)
if(MKTEMP)
execute_process(COMMAND mktemp
OUTPUT_VARIABLE _working_dir
OUTPUT_STRIP_TRAILING_WHITESPACE
${_output_quiet}
COMMAND_ECHO ${_command_echo})
# mktemp formats it how we want it
else()
# Ummm... Linux you can do better!
set(_tmpdir "/tmp")
endif()
endif()
if(NOT DEFINED _working_dir)
string(RANDOM subdir)
set(_working_dir "${_tmpdir}/tmp.${subdir}")
endif()
if(NOT EXISTS "${_working_dir}")
file(MAKE_DIRECTORY "${_working_dir}")
endif()
endif()
if(_prepend_to_path)
# Ensure the PATH is configured
string(FIND "$ENV{PATH}" "${_working_dir}" _pathloc)
if(NOT $_pathloc EQUAL 0)
set(ENV{PATH} "${_working_dir}:$ENV{PATH}")
endif()
endif()
# First ensure the binary doesn't already exist
find_program(_${RESULT_VARIABLE} "${_name}" HINTS "${_working_dir}")
set(_binary_path "${_working_dir}/${_name}")
if(NOT _${RESULT_VARIABLE})
message(STATUS "Downloading ${_name} from ${_url}...")
file(DOWNLOAD
"${_url}"
"${_binary_path}"
STATUS DOWNLOAD_STATUS)
# Check if download was successful.
list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
list(GET DOWNLOAD_STATUS 1 ERROR_MESSAGE)
if(NOT ${STATUS_CODE} EQUAL 0)
file(REMOVE "${_binary_path}")
message(FATAL_ERROR "Error downloading ${_url} ${ERROR_MESSAGE}")
endif()
# Ensure the file is executable
file(CHMOD "${_binary_path}" PERMISSIONS
OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_WRITE GROUP_READ)
# Ensure it's found
find_program(_${RESULT_VARIABLE} "${_name}" HINTS "${_working_dir}" REQUIRED)
endif()
# We need to create a subdirectory for this binary and symlink it's AppRun to where it's expected
if(_${RESULT_VARIABLE}_NEEDS_FUSE AND NOT _FUSE_FOUND)
if(NOT COMMAND create_symlink)
include(CreateSymlink)
endif()
# extract appimage
execute_process(COMMAND "${_${RESULT_VARIABLE}}" --appimage-extract
WORKING_DIRECTORY "${_working_dir}"
COMMAND_ECHO ${_command_echo}
${_output_quiet}
${_error_quiet}
COMMAND_ERROR_IS_FATAL ANY)
# move extracted files to dedicated location (e.g. ".linuxdeploy-x86_64.AppImage/squashfs-root/")
file(MAKE_DIRECTORY "${_working_dir}/.${_name}/")
file(RENAME "${_working_dir}/squashfs-root/" "${_working_dir}/.${_name}/squashfs-root/")
# remove the unusable binary
file(REMOVE "${_${RESULT_VARIABLE}}")
# symlink the expected binary name to the AppRun file
create_symlink("${_working_dir}/.${_name}/squashfs-root/AppRun" "${_${RESULT_VARIABLE}}")
endif()
# We need to create a subdirectory for this binary and symlink it's program name to where it's expected
if(_${RESULT_VARIABLE}_USES_MAKESELF)
if(NOT COMMAND create_symlink)
include(CreateSymlink)
endif()
execute_process(COMMAND "${_${RESULT_VARIABLE}}"
--target "${_working_dir}/.${_name}"
--nox11
${_output_quiet}
COMMAND_ECHO ${_command_echo}
${_output_quiet}
${_error_quiet}
COMMAND_ERROR_IS_FATAL ANY)
# remove the unusable binary
file(REMOVE "${_${RESULT_VARIABLE}}")
# find it by name, use it
find_program(_${RESULT_VARIABLE}_REAL "${_name}"
HINTS "${_working_dir}/.${_name}" "${_working_dir}/.${_name}/bin" "${_working_dir}/.${_name}/usr/bin"
REQUIRED)
if("${_name}" STREQUAL "makeself.sh")
# skip symlink, it will prevent makeself.sh from finding makeself-header.sh
set(_${RESULT_VARIABLE} "${_${RESULT_VARIABLE}_REAL}")
else()
# symlink the expected binary name
create_symlink("${_${RESULT_VARIABLE}_REAL}" "${_${RESULT_VARIABLE}}")
endif()
endif()
# Test the binary
# - TODO: Add support for bad binaries that set "$?" to an error code for no good reason
# - TODO: Add support for Windows binaries expecting "/?" instead of "--help"
message(STATUS "Testing that ${_name} works on this system...")
if(_${RESULT_VARIABLE}_USES_MAKESELF)
set(_test_param "--version")
else()
set(_test_param "--help")
endif()
execute_process(COMMAND "${_${RESULT_VARIABLE}}" ${_test_param}
COMMAND_ECHO ${_command_echo}
${_output_quiet}
${_error_quiet}
COMMAND_ERROR_IS_FATAL ANY)
message(STATUS "The binary \"${_${RESULT_VARIABLE}}\" is now available")
set(${RESULT_VARIABLE} "${_${RESULT_VARIABLE}}")
endmacro()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment