1IF (NOT CBDownloadDeps_INCLUDED)
2  SET (CBDownloadDeps_INCLUDED 1)
3
4  INCLUDE (ParseArguments)
5  INCLUDE (PlatformIntrospection)
6
7  # Given a file and a corresponding .md5 file, verify that the file's MD5
8  # checksum matches the contents of the .md5 file.
9  MACRO (_CHECK_MD5 file md5file retval)
10    IF (NOT EXISTS "${md5file}")
11      MESSAGE (FATAL_ERROR "Missing .md5 file ${md5file}!")
12    ENDIF (NOT EXISTS "${md5file}")
13    FILE (MD5 "${file}" _actual_md5)
14    FILE (STRINGS "${md5file}" _expected_md5)
15    IF (_actual_md5 STREQUAL _expected_md5)
16      SET (${retval} 1)
17    ELSE (_actual_md5 STREQUAL _expected_md5)
18      SET (${retval} 0)
19    ENDIF (_actual_md5 STREQUAL _expected_md5)
20  ENDMACRO (_CHECK_MD5)
21
22  # Downloads a file from a URL to a local file, raising any errors.
23  FUNCTION (_DOWNLOAD_FILE url file)
24    FILE (DOWNLOAD "${url}" "${file}" STATUS _stat SHOW_PROGRESS)
25    LIST (GET _stat 0 _retval)
26    IF (_retval)
27      # Don't leave corrupt/empty downloads
28      IF (EXISTS "${file}")
29        FILE (REMOVE "${file}")
30      ENDIF (EXISTS "${file}")
31      LIST (GET _stat 0 _errcode)
32      LIST (GET _stat 1 _message)
33      MESSAGE (FATAL_ERROR "Error downloading ${url}: ${_message} (${_errcode})")
34    ENDIF (_retval)
35  ENDFUNCTION (_DOWNLOAD_FILE)
36
37  # Downloads a specific URL to a file with the same name in the cache dir.
38  # First checks the cache for an up-to-date copy based on md5.
39  # Sets the variable named by "var" to the locally-cached path.
40  FUNCTION (_DOWNLOAD_URL_TO_CACHE url var)
41    # Compute local name for cache.
42    GET_FILENAME_COMPONENT (_file "${url}" NAME)
43    SET (_md5file "${_file}.md5")
44
45    # Compute local full paths to cached files.
46    SET (_cache_file_path "${CB_DOWNLOAD_DEPS_CACHE}/${_file}")
47    SET (_cache_md5file_path "${CB_DOWNLOAD_DEPS_CACHE}/${_md5file}")
48
49    # "Return" the cached file path.
50    SET (${var} "${_cache_file_path}" PARENT_SCOPE)
51
52    _CHECK_CACHED_DEP_FILE("${_cache_file_path}" "${_cache_md5file_path}" _found_cached)
53
54    IF (_found_cached)
55      RETURN ()
56    ENDIF (_found_cached)
57
58    # File not found in cache or cache corrupt - download new.
59
60    # First compute the URL for the .md5 and download it. For historical
61    # reasons, if the url's extension is ".tgz", the MD5 url will be same as
62    # the url with ".tgz" replaced by ".md5". Otherwise, the MD5 url will
63    # simply be the url with an additional ".md5" extension.
64    IF (url MATCHES "\\.tgz$")
65      STRING (REGEX REPLACE "\\.tgz$" ".md5" _md5url "${url}")
66    ELSE ()
67      SET (_md5url "${_url}.md5")
68    ENDIF ()
69    MESSAGE (STATUS "Downloading dependency md5: ${_md5file}")
70    _DOWNLOAD_FILE ("${_md5url}" "${_cache_md5file_path}")
71    MESSAGE (STATUS "Downloading dependency: ${_file}")
72    _DOWNLOAD_FILE ("${url}" "${_cache_file_path}")
73    _CHECK_MD5 ("${_cache_file_path}" "${_cache_md5file_path}" _md5equal)
74    IF (NOT _md5equal)
75      MESSAGE (FATAL_ERROR "Downloaded file ${_cache_file_path} failed md5 sum check!")
76    ENDIF ()
77  ENDFUNCTION (_DOWNLOAD_URL_TO_CACHE)
78
79  FUNCTION (_CHECK_CACHED_DEP_FILE path md5path var)
80    SET(${var} FALSE PARENT_SCOPE)
81
82    IF (EXISTS "${path}")
83      IF (EXISTS "${md5path}")
84        _CHECK_MD5 ("${path}" "${md5path}" _md5equal)
85        IF (_md5equal)
86          MESSAGE (STATUS "Dependency '${path}' found in cache")
87          SET(${var} TRUE PARENT_SCOPE)
88          RETURN ()
89        ELSE (_md5equal)
90          MESSAGE (WARNING "Cached download for dependency '${path}' has "
91            "incorrect MD5! Will re-download!")
92        ENDIF (_md5equal)
93      ELSE (EXISTS "${md5path}")
94        MESSAGE (WARNING "Cached download for dependency '${md5path}' is missing "
95          "md5 file! Will re-download!")
96      ENDIF (EXISTS "${md5path}")
97    ENDIF (EXISTS "${path}")
98  ENDFUNCTION ()
99
100  FUNCTION (_GET_DEP_FILENAME name version var)
101    _DETERMINE_PLATFORM (_platform)
102    _DETERMINE_ARCH (_arch)
103
104    # Compute relative paths to dependency on local filesystem
105    # and in remote repository
106    SET (${var} "${name}-${_platform}-${_arch}-${version}.tgz" PARENT_SCOPE)
107  ENDFUNCTION ()
108
109  # Downloads a dependency to the cache dir. First checks the cache for
110  # an up-to-date copy based on md5.  Sets the variable named by 'var'
111  # to the downloaded dependency .tgz.
112  FUNCTION (_DOWNLOAD_DEP name version var)
113    _GET_DEP_FILENAME("${name}" "${version}" _rel_path)
114    SET (_repo_url "${CB_DOWNLOAD_DEPS_REPO}/${name}/${version}/${_rel_path}")
115    _DOWNLOAD_URL_TO_CACHE ("${_repo_url}" _cachefile)
116    SET (${var} "${_cachefile}" PARENT_SCOPE)
117  ENDFUNCTION (_DOWNLOAD_DEP)
118
119  # Unpack an archive file into a directory, with error checking.
120  FUNCTION (EXPLODE_ARCHIVE file dir)
121    FILE (MAKE_DIRECTORY "${dir}")
122    EXECUTE_PROCESS (COMMAND "${CMAKE_COMMAND}" -E
123      tar xf "${file}"
124      WORKING_DIRECTORY "${dir}"
125      RESULT_VARIABLE _explode_result
126      ERROR_VARIABLE _explode_stderr)
127    IF(_explode_result)
128      FILE (REMOVE_RECURSE "${dir}")
129      FILE (REMOVE "${file}")
130      MESSAGE (FATAL_ERROR "Failed to extract dependency ${file} - file corrupt? "
131        "It has been deleted, please try again.\n ${_explode_stderr}")
132    ENDIF(_explode_result)
133  ENDFUNCTION (EXPLODE_ARCHIVE)
134
135  # Declare a dependency
136  FUNCTION (DECLARE_DEP name)
137    PARSE_ARGUMENTS (dep "PLATFORMS" "VERSION" "SKIP" ${ARGN})
138
139    # If this dependency has already been declared, skip it.
140    # Exception: if we are building the cbdeps packages themselves then
141    # allow duplicates; as each cbdep may need the package for it's
142    # own build.
143    SET (_prop_name "CB_DOWNLOADED_DEP_${name}")
144    GET_PROPERTY (_declared GLOBAL PROPERTY ${_prop_name} SET)
145    IF (_declared AND NOT "${PROJECT_NAME}" STREQUAL "cbdeps_packages")
146      MESSAGE (STATUS "Dependency ${name} already declared, skipping...")
147      RETURN ()
148    ENDIF (_declared AND NOT "${PROJECT_NAME}" STREQUAL "cbdeps_packages")
149
150    # If this dependency declares PLATFORM, ensure that we are running on
151    # one of those platforms.
152    _DETERMINE_PLATFORM (_this_platform)
153    CB_GET_SUPPORTED_PLATFORM (_supported_platform)
154    LIST (LENGTH dep_PLATFORMS _num_platforms)
155    IF (_num_platforms GREATER 0)
156      SET (_found_platform 0)
157      FOREACH (_platform ${dep_PLATFORMS})
158        IF ("${_this_platform}" STREQUAL "${_platform}")
159          SET (_found_platform 1)
160          BREAK ()
161        ENDIF ("${_this_platform}" STREQUAL "${_platform}")
162      ENDFOREACH (_platform)
163      IF (NOT _found_platform AND NOT _supported_platform)
164        # check if we maybe have locally built dep file
165        _GET_DEP_FILENAME("${name}" "${dep_VERSION}" _dep_filename)
166        SET(_dep_path "${CB_DOWNLOAD_DEPS_CACHE}/${_dep_filename}")
167        SET(_dep_md5path "${CB_DOWNLOAD_DEPS_CACHE}/${_dep_filename}.md5")
168
169        _CHECK_CACHED_DEP_FILE("${_dep_path}" "${_dep_md5path}" _dep_found)
170
171        IF (_dep_found)
172          MESSAGE (STATUS "Found locally built dependency file ${_dep_path}. "
173            "Going to use it even though the platform ${_this_platform} is unsupported")
174          SET (_found_platform 1)
175        ENDIF ()
176      ENDIF (NOT _found_platform AND NOT _supported_platform)
177      IF (NOT _found_platform)
178        MESSAGE (STATUS "Dependency ${name} (${dep_VERSION}) not declared for platform "
179          "${_this_platform}, skipping...")
180        RETURN ()
181      ENDIF ()
182    ENDIF (_num_platforms GREATER 0)
183
184    # Remember that this dependency has been declared.
185    SET_PROPERTY (GLOBAL PROPERTY ${_prop_name} 1)
186
187    IF (dep_SKIP)
188      MESSAGE (STATUS "Skipping download of dependency ${name} as requested")
189      RETURN ()
190    ENDIF (dep_SKIP)
191    IF (NOT dep_VERSION)
192      MESSAGE (FATAL_ERROR "Must specify either VERSION or SKIP for "
193        "dependency '${name}' in dependency manifest.")
194    ENDIF (NOT dep_VERSION)
195
196    # Compute paths for exploded tgz and binary dir
197    SET (_explode_dir "${CMAKE_CURRENT_BINARY_DIR}/${name}.exploded")
198    SET (_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/${name}.binary")
199
200    # See if dependency is already downloaded. We assume the existence of a
201    # VERSION file which matches the current version is sufficient.
202    IF (EXISTS "${_explode_dir}/VERSION.txt")
203      FILE (STRINGS "${_explode_dir}/VERSION.txt" EXPLODED_VERSION)
204    ELSE (EXISTS "${_explode_dir}/VERSION.txt")
205      SET (EXPLODED_VERSION "<none>")
206    ENDIF (EXISTS "${_explode_dir}/VERSION.txt")
207
208    MESSAGE (STATUS "Checking exploded ${name} version ${EXPLODED_VERSION} against ${dep_VERSION}")
209    IF (EXPLODED_VERSION STREQUAL ${dep_VERSION})
210      MESSAGE (STATUS "Dependency '${name} (${dep_VERSION})' already downloaded")
211    ELSE (EXPLODED_VERSION STREQUAL ${dep_VERSION})
212      _DOWNLOAD_DEP (${name} ${dep_VERSION} _cachedep)
213
214      # Explode tgz into build directory.
215      MESSAGE (STATUS "Installing dependency: ${name}-${dep_VERSION}...")
216      EXPLODE_ARCHIVE ("${_cachedep}" "${_explode_dir}")
217      FILE (WRITE "${_explode_dir}/VERSION.txt" ${dep_VERSION})
218    ENDIF (EXPLODED_VERSION STREQUAL ${dep_VERSION})
219
220    # Always add the dep subdir; this will "re-install" the dep every time you
221    # run CMake, which might be wasteful, but at least should be safe.
222    FILE (MAKE_DIRECTORY "${_binary_dir}")
223    IF (EXISTS ${_explode_dir}/CMakeLists.txt)
224      ADD_SUBDIRECTORY ("${_explode_dir}" "${_binary_dir}" EXCLUDE_FROM_ALL)
225    ENDIF (EXISTS ${_explode_dir}/CMakeLists.txt)
226  ENDFUNCTION (DECLARE_DEP)
227
228  # Download and cache a specific version of Go, and explode it into the
229  # *cache* directory. Sets the variable named by "var" to point to
230  # the GOROOT in the exploded directory.
231  FUNCTION (GET_GO_VERSION GOVERSION var)
232    # Unlike DOWNLOAD_DEP(), we explode Go downloads into the cache directory,
233    # not the binary directory. This means we don't need to re-explode it
234    # every time we clean the binary directory. We could do the same for
235    # other downloaded deps (and probably should), but we have less control
236    # other whether any parts of the build do naughty things like modify the
237    # exploded dep contents.
238    _DETERMINE_ARCH (_arch)
239    SET (_explode_dir "${CB_DOWNLOAD_DEPS_CACHE}/exploded/${_arch}/go-${GOVERSION}")
240    SET (_goroot "${_explode_dir}/go")
241    IF (WIN32)
242      SET (_goexe "${_goroot}/bin/go.exe")
243    ELSE ()
244      SET (_goexe "${_goroot}/bin/go")
245    ENDIF ()
246    SET (${var} "${_goroot}" PARENT_SCOPE)
247
248    # We assume the go binary existing is sufficient to say that the requested
249    # go version has been downloaded successfully.
250    IF (EXISTS "${_goexe}")
251      RETURN ()
252    ENDIF ()
253
254    # Otherwise, download the correct version for the current platform.
255    _DETERMINE_PLATFORM (_platform)
256    STRING (SUBSTRING "${_platform}" 0 6 _platform_head)
257    IF (_platform STREQUAL "macosx")
258      IF ("${GOVERSION}" MATCHES "^1.4.[0-9]+$")
259        SET (_gofile "go${GOVERSION}.darwin-amd64-osx10.8.tar.gz")
260      ELSE ()
261        SET (_gofile "go${GOVERSION}.darwin-amd64.tar.gz")
262      ENDIF ()
263    ELSEIF (_platform_head STREQUAL "window")
264      IF (_arch STREQUAL "x86")
265        SET (_arch "386")
266      ENDIF ()
267      SET (_gofile "go${GOVERSION}.windows-${_arch}.zip")
268    ELSEIF (_platform STREQUAL "freebsd")
269      SET (_gofile "go${GOVERSION}.freebsd-amd64.tar.gz")
270    ELSE ()
271      # Presumed Linux
272      SET (_gofile "go${GOVERSION}.linux-amd64.tar.gz")
273    ENDIF ()
274    SET (_cachefile "${CB_DOWNLOAD_DEPS_CACHE}/${_gofile}")
275    IF (NOT EXISTS "${_cachefile}")
276      MESSAGE (STATUS "Golang version ${GOVERSION} not found in cache, "
277        "downloading...")
278      _DOWNLOAD_FILE ("${GO_DOWNLOAD_REPO}/${_gofile}" "${_cachefile}")
279    ENDIF ()
280    MESSAGE (STATUS "Installing Golang version ${GOVERSION} in cache...")
281    EXPLODE_ARCHIVE ("${_cachefile}" "${_explode_dir}")
282
283    IF (NOT EXISTS "${_goexe}")
284      MESSAGE (FATAL_ERROR "Downloaded go archive ${_gofile}"
285        " failed to unpack correctly - ${_goexe} does not exist!")
286    ENDIF ()
287  ENDFUNCTION (GET_GO_VERSION)
288
289  # Start of CBDeps 2.0 - download and cache the new 'cbdep' tool
290  SET (CBDEP_VERSION 0.9.8)
291  FUNCTION (GET_CBDEP)
292    _DETERMINE_PLATFORM (_platform)
293    STRING (SUBSTRING "${_platform}" 0 6 _platform_head)
294    IF (_platform STREQUAL "macosx")
295      SET (_cbdepfile "cbdep-${CBDEP_VERSION}-darwin")
296    ELSEIF (_platform_head STREQUAL "window")
297      SET (_cbdepfile "cbdep-${CBDEP_VERSION}-windows.exe")
298    ELSE ()
299      # Presumed Linux
300      SET (_cbdepfile "cbdep-${CBDEP_VERSION}-linux")
301    ENDIF ()
302    SET (CBDEP_CACHE "${CB_DOWNLOAD_DEPS_CACHE}/cbdep/${CBDEP_VERSION}/${_cbdepfile}"
303      CACHE INTERNAL "Path to cbdep cached download")
304    SET (CBDEP "${PROJECT_BINARY_DIR}/tlm/${_cbdepfile}"
305      CACHE INTERNAL "Path to cbdep executable")
306    IF (NOT EXISTS "${CBDEP_CACHE}")
307      MESSAGE (STATUS "Downloading cbdep ${CBDEP_VERSION}")
308      _DOWNLOAD_FILE (
309        "http://packages.couchbase.com/cbdep/${CBDEP_VERSION}/${_cbdepfile}"
310        "${CBDEP_CACHE}")
311    ENDIF ()
312    FILE (COPY "${CBDEP_CACHE}" DESTINATION "${PROJECT_BINARY_DIR}/tlm"
313      FILE_PERMISSIONS OWNER_EXECUTE OWNER_READ OWNER_WRITE)
314    MESSAGE (STATUS "Using cbdep at ${CBDEP}")
315  ENDFUNCTION (GET_CBDEP)
316
317  CB_GET_SUPPORTED_PLATFORM (_supported_platform)
318  IF (_supported_platform)
319    SET (CB_DOWNLOAD_DEPS_REPO
320      "http://packages.couchbase.com/couchbase-server/deps"
321      CACHE STRING "URL of third-party dependency repository")
322  ELSE ()
323    SET (CB_DOWNLOAD_DEPS_REPO
324      "http://packages.couchbase.com/couchbase-server/deps-unsupported"
325      CACHE STRING "URL of third-party dependency repository")
326  ENDIF ()
327  SET (GO_DOWNLOAD_REPO "http://storage.googleapis.com/golang"
328    CACHE STRING "URL of Golang downloads repository")
329
330  # Default download cache in user's home directory; may be overridden
331  # by CB_DOWNLOAD_DEPS_CACHE environment variable or by
332  # -DCB_DOWNLOAD_DEPS_CACHE on the CMake line.
333  IF (DEFINED ENV{CB_DOWNLOAD_DEPS_CACHE})
334    SET (_cache_dir_default "$ENV{CB_DOWNLOAD_DEPS_CACHE}")
335  ELSEIF (WIN32)
336    SET (_cache_dir_default "$ENV{HOMEDRIVE}/$ENV{HOMEPATH}/cbdepscache")
337  ELSE (DEFINED ENV{CB_DOWNLOAD_DEPS_CACHE})
338    # Linux / Mac
339    SET (_cache_dir_default "$ENV{HOME}/.cbdepscache")
340  ENDIF (DEFINED ENV{CB_DOWNLOAD_DEPS_CACHE})
341  SET (CB_DOWNLOAD_DEPS_CACHE "${_cache_dir_default}" CACHE PATH
342    "Path to cache downloaded third-party dependencies")
343  FILE (MAKE_DIRECTORY "${CB_DOWNLOAD_DEPS_CACHE}")
344  MESSAGE (STATUS "Third-party dependencies will be cached in "
345    "${CB_DOWNLOAD_DEPS_CACHE}")
346
347  GET_CBDEP ()
348ENDIF (NOT CBDownloadDeps_INCLUDED)
349