1# 2# Copyright 2020, Data61, CSIRO (ABN 41 687 119 230) 3# 4# SPDX-License-Identifier: BSD-2-Clause 5# 6 7# Try to set the cache dir based on environment variable, but override 8# with value passed in as CMake -D argument. 9set(cache_dir "$ENV{SEL4_CACHE_DIR}") 10if(NOT ${SEL4_CACHE_DIR} STREQUAL "") 11 set(cache_dir "${SEL4_CACHE_DIR}") 12endif() 13# Convert to an absolute path. 14if((NOT ("${cache_dir}" STREQUAL "")) AND NOT IS_ABSOLUTE cache_dir) 15 get_filename_component(cache_dir "${cache_dir}" ABSOLUTE BASE_DIR "${CMAKE_BINARY_DIR}") 16endif() 17set(MEMOIZE_CACHE_DIR "${cache_dir}" CACHE INTERNAL "" FORCE) 18 19# This function wraps a call to add_custom_command and may instead use an alternative cached 20# copy if it can already find a version that has been built before. 21# If git_directory is provided and the git directory has any uncommitted changes, then the 22# cache is bypassed and will always build from source. 23# key: A key to distinguish different memoized commands by, and also used in diagnostic output 24# replace_dir: Directory to tar and cache. This tar'd directory is what gets expanded in cache hits. 25# git_directory: A directory in a Git repository for performing clean/dirty check. 26# extra_arguments: A string of extra arguments that if change invalidate previous cache entries. 27# replace_files: Subset list of files to tar from replace_dir. If empty string then the whole dir 28# will be cached. 29function(memoize_add_custom_command key replace_dir git_directory extra_arguments replace_files) 30 31 message(STATUS "Detecting cached version of: ${key}") 32 33 # If a cache directory isn't set then call the underlying function and return. 34 if("${MEMOIZE_CACHE_DIR}" STREQUAL "") 35 message( 36 STATUS 37 " No cache path given. Set SEL4_CACHE_DIR to a path to enable caching binary artifacts." 38 ) 39 add_custom_command(${ARGN}) 40 return() 41 endif() 42 43 set(dirty OFF) 44 45 # Check if the git directory has any changes. 46 # Sets dirty to ON if there are changes 47 # sets git_source_hash to the git hash 48 if(NOT ${git_directory} STREQUAL "") 49 find_package(Git REQUIRED) 50 51 execute_process( 52 COMMAND 53 "${GIT_EXECUTABLE}" diff --exit-code HEAD -- 54 WORKING_DIRECTORY "${git_directory}" 55 RESULT_VARIABLE res 56 OUTPUT_VARIABLE out 57 ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE 58 ) 59 if(res EQUAL 0) 60 61 else() 62 set(dirty ON) 63 endif() 64 execute_process( 65 COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD 66 WORKING_DIRECTORY "${git_directory}" 67 RESULT_VARIABLE res 68 OUTPUT_VARIABLE out 69 ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE 70 ) 71 set(git_source_hash ${out}) 72 endif() 73 74 # Set the directory if no replace_files are set 75 if(replace_files STREQUAL "") 76 set(replace_files .) 77 endif() 78 79 # Set the cache dir based on a hash of the git hash and extra arguments 80 # Note: We don't use the entire args to add_custom_command as inputs to the 81 # hash as they will change based on the path of the build directory. 82 # also it's too hard to guarantee that we can perfectly know whether a cache 83 # entry is valid or not, so delegate figuring out to the caller. 84 set(hash_string ${extra_arguments} ${git_source_hash}) 85 string(MD5 hash "${hash_string}") 86 set(cache_dir ${MEMOIZE_CACHE_DIR}/${key}/${hash}) 87 if(dirty) 88 # The git directory has changed, so don't use the cache 89 message(STATUS " ${key} is dirty - will build from source") 90 add_custom_command(${ARGN}) 91 else() 92 if(EXISTS ${cache_dir}/code.tar.gz) 93 # Cache hit. Create a different call to add_custom_command that 94 # merely unpacks the result from the last build instance into the 95 # target directory. 96 message(STATUS " Found valid cache entry for ${key}") 97 98 # Set a cmake rebuild dependency on the cache file so if it changes 99 # cmake will get automatically rerun 100 set_property( 101 DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 102 APPEND 103 PROPERTY CMAKE_CONFIGURE_DEPENDS "${cache_dir}/code.tar.gz" 104 ) 105 106 # As we use the same output file we extract it from the args passed in. 107 if(NOT "${ARGV5}" STREQUAL "OUTPUT") 108 message(FATAL_ERROR "OUTPUT must be first argument to this function") 109 endif() 110 add_custom_command( 111 OUTPUT 112 # This has to match up with the OUTPUT variable of the call to add_custom_command 113 ${ARGV6} 114 # If we have to rebuild, first clear the temporary build directory as 115 # we have no correctly captured the output files or dependencies 116 COMMAND rm -r ${replace_dir} 117 COMMAND mkdir -p ${replace_dir} 118 COMMAND 119 tar -C ${replace_dir} -xf ${cache_dir}/code.tar.gz 120 DEPENDS ${deps} COMMAND_EXPAND_LISTS 121 COMMENT "Using cache ${key} build" 122 ) 123 else() 124 # Don't have a previous build in the cache. Create the rule but add 125 # some commands on the end to cache the result in our cache. 126 message(STATUS " Not found cache entry for ${key} - will build from source") 127 add_custom_command( 128 ${ARGN} 129 COMMAND mkdir -p ${cache_dir} 130 COMMAND 131 tar -zcf code.tar.gz -C ${replace_dir} ${replace_files} 132 COMMAND mv code.tar.gz ${cache_dir}/ 133 ) 134 endif() 135 endif() 136endfunction() 137