summaryrefslogtreecommitdiff
path: root/cmake/mason.cmake
blob: bc31feeb5f26704bef71dd1e077b0bf3f7ceaa79 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# Mason CMake

include(CMakeParseArguments)

function(mason_detect_platform)
    # Determine platform
    if(NOT MASON_PLATFORM)
        # we call uname -s manually here since
        # CMAKE_HOST_SYSTEM_NAME will not be defined before the project() call
        execute_process(
            COMMAND uname -s
            OUTPUT_VARIABLE UNAME
            OUTPUT_STRIP_TRAILING_WHITESPACE)

        if (UNAME STREQUAL "Darwin")
            set(MASON_PLATFORM "osx" PARENT_SCOPE)
        else()
            set(MASON_PLATFORM "linux" PARENT_SCOPE)
        endif()
    endif()

    # Determine platform version string
    if(NOT MASON_PLATFORM_VERSION)
        # Android Studio only passes ANDROID_ABI, but we need to adjust that to the Mason
        if(MASON_PLATFORM STREQUAL "android" AND NOT MASON_PLATFORM_VERSION)
            if (ANDROID_ABI STREQUAL "armeabi")
                set(MASON_PLATFORM_VERSION "arm-v5-9" PARENT_SCOPE)
            elseif (ANDROID_ABI STREQUAL "armeabi-v7a")
                set(MASON_PLATFORM_VERSION "arm-v7-9" PARENT_SCOPE)
            elseif (ANDROID_ABI STREQUAL "arm64-v8a")
                set(MASON_PLATFORM_VERSION "arm-v8-21" PARENT_SCOPE)
            elseif (ANDROID_ABI STREQUAL "x86")
                set(MASON_PLATFORM_VERSION "x86-9" PARENT_SCOPE)
            elseif (ANDROID_ABI STREQUAL "x86_64")
                set(MASON_PLATFORM_VERSION "x86-64-21" PARENT_SCOPE)
            elseif (ANDROID_ABI STREQUAL "mips")
                set(MASON_PLATFORM_VERSION "mips-9" PARENT_SCOPE)
            elseif (ANDROID_ABI STREQUAL "mips64")
                set(MASON_PLATFORM_VERSION "mips-64-9" PARENT_SCOPE)
            else()
                message(FATAL_ERROR "Unknown ANDROID_ABI '${ANDROID_ABI}'.")
            endif()
        elseif(MASON_PLATFORM STREQUAL "ios")
            set(MASON_PLATFORM_VERSION "8.0" PARENT_SCOPE)
        else()
            execute_process(
                COMMAND uname -m
                OUTPUT_VARIABLE MASON_PLATFORM_VERSION
                OUTPUT_STRIP_TRAILING_WHITESPACE)
            set(MASON_PLATFORM_VERSION "${MASON_PLATFORM_VERSION}" PARENT_SCOPE)
        endif()
    endif()
endfunction()

function(mason_use _PACKAGE)
    if(NOT _PACKAGE)
        message(FATAL_ERROR "[Mason] No package name given")
    endif()

    cmake_parse_arguments("" "HEADER_ONLY" "VERSION" "" ${ARGN})

    if(_UNPARSED_ARGUMENTS)
        message(FATAL_ERROR "[Mason] mason_use() called with unrecognized arguments: ${_UNPARSED_ARGUMENTS}")
    endif()

    if(NOT _VERSION)
        message(FATAL_ERROR "[Mason] Specifying a version is required")
    endif()

    if(MASON_PACKAGE_${_PACKAGE}_INVOCATION STREQUAL "${MASON_INVOCATION}")
        # Check that the previous invocation of mason_use didn't select another version of this package
        if(NOT MASON_PACKAGE_${_PACKAGE}_VERSION STREQUAL ${_VERSION})
            message(FATAL_ERROR "[Mason] Already using ${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_VERSION}. Cannot select version ${_VERSION}.")
        endif()
    else()
        if(_HEADER_ONLY)
            set(_PLATFORM_ID "headers")
        else()
            set(_PLATFORM_ID "${MASON_PLATFORM}-${MASON_PLATFORM_VERSION}")
        endif()

        set(_SLUG "${_PLATFORM_ID}/${_PACKAGE}/${_VERSION}")
        set(_INSTALL_PATH "${MASON_PACKAGE_DIR}/${_SLUG}")
        file(RELATIVE_PATH _INSTALL_PATH_RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${_INSTALL_PATH}")

        if(NOT EXISTS "${_INSTALL_PATH}")
            set(_CACHE_PATH "${MASON_PACKAGE_DIR}/.binaries/${_SLUG}.tar.gz")
            if (NOT EXISTS "${_CACHE_PATH}")
                # Download the package
                set(_URL "${MASON_REPOSITORY}/${_SLUG}.tar.gz")
                message("[Mason] Downloading package ${_URL}...")

                set(_FAILED)
                set(_ERROR)
                # Note: some CMake versions are compiled without SSL support
                get_filename_component(_CACHE_DIR "${_CACHE_PATH}" DIRECTORY)
                file(MAKE_DIRECTORY "${_CACHE_DIR}")
                execute_process(
                    COMMAND curl --retry 3 -s -f -S -L "${_URL}" -o "${_CACHE_PATH}.tmp"
                    RESULT_VARIABLE _FAILED
                    ERROR_VARIABLE _ERROR)
                if(_FAILED)
                    message(FATAL_ERROR "[Mason] Failed to download ${_URL}: ${_ERROR}")
                else()
                    # We downloaded to a temporary file to prevent half-finished downloads
                    file(RENAME "${_CACHE_PATH}.tmp" "${_CACHE_PATH}")
                endif()
            endif()

            # Unpack the package
            message("[Mason] Unpacking package to ${_INSTALL_PATH_RELATIVE}...")
            file(MAKE_DIRECTORY "${_INSTALL_PATH}")
            execute_process(
                COMMAND ${CMAKE_COMMAND} -E tar xzf "${_CACHE_PATH}"
                WORKING_DIRECTORY "${_INSTALL_PATH}")
        endif()

        # Error out if there is no config file.
        if(NOT EXISTS "${_INSTALL_PATH}/mason.ini")
            message(FATAL_ERROR "[Mason] Could not find mason.ini for package ${_PACKAGE} ${_VERSION}")
        endif()

        set(MASON_PACKAGE_${_PACKAGE}_PREFIX "${_INSTALL_PATH}" CACHE STRING "${_PACKAGE} ${_INSTALL_PATH}" FORCE)
        mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_PREFIX)

        # Load the configuration from the ini file
        file(STRINGS "${_INSTALL_PATH}/mason.ini" _CONFIG_FILE)
        foreach(_LINE IN LISTS _CONFIG_FILE)
            string(REGEX MATCH "^([a-z_]+) *= *" _KEY "${_LINE}")
            if (_KEY)
                string(LENGTH "${_KEY}" _KEY_LENGTH)
                string(SUBSTRING "${_LINE}" ${_KEY_LENGTH} -1 _VALUE)
                string(REGEX REPLACE ";.*$" "" _VALUE "${_VALUE}") # Trim trailing commas
                string(REPLACE "{prefix}" "${_INSTALL_PATH}" _VALUE "${_VALUE}")
                string(STRIP "${_VALUE}" _VALUE)
                string(REPLACE "=" "" _KEY "${_KEY}")
                string(STRIP "${_KEY}" _KEY)
                string(TOUPPER "${_KEY}" _KEY)
                if(_KEY STREQUAL "INCLUDE_DIRS" OR _KEY STREQUAL "STATIC_LIBS" )
                    separate_arguments(_VALUE)
                endif()
                set(MASON_PACKAGE_${_PACKAGE}_${_KEY} "${_VALUE}" CACHE STRING "${_PACKAGE} ${_KEY}" FORCE)
                mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_${_KEY})
            endif()
        endforeach()

        # Compare version in the package to catch errors early on
        if(NOT _VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_VERSION)
            message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has version '${MASON_PACKAGE_${_PACKAGE}_VERSION}', but required '${_VERSION}'")
        endif()

        if(NOT _PACKAGE STREQUAL MASON_PACKAGE_${_PACKAGE}_NAME)
            message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has name '${MASON_PACKAGE_${_PACKAGE}_NAME}', but required '${_NAME}'")
        endif()

        if(NOT _HEADER_ONLY)
            if(NOT MASON_PLATFORM STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM)
                message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform '${MASON_PACKAGE_${_PACKAGE}_PLATFORM}', but required '${MASON_PLATFORM}'")
            endif()

            if(NOT MASON_PLATFORM_VERSION STREQUAL MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION)
                message(FATAL_ERROR "[Mason] Package at ${_INSTALL_PATH_RELATIVE} has platform version '${MASON_PACKAGE_${_PACKAGE}_PLATFORM_VERSION}', but required '${MASON_PLATFORM_VERSION}'")
            endif()
        endif()

        # Concatenate the static libs and libraries
        set(_LIBRARIES)
        list(APPEND _LIBRARIES ${MASON_PACKAGE_${_PACKAGE}_STATIC_LIBS} ${MASON_PACKAGE_${_PACKAGE}_LDFLAGS})
        set(MASON_PACKAGE_${_PACKAGE}_LIBRARIES "${_LIBRARIES}" CACHE STRING "${_PACKAGE} _LIBRARIES" FORCE)
        mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARIES)

        if(NOT _HEADER_ONLY)
            string(REGEX MATCHALL "(^| +)-L *([^ ]+)" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LDFLAGS}")
            string(REGEX REPLACE "(^| +)-L *" "\\1" MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}")
            set(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS "${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" CACHE STRING "${_PACKAGE} ${MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS}" FORCE)
            mark_as_advanced(MASON_PACKAGE_${_PACKAGE}_LIBRARY_DIRS)
        endif()

        # Store invocation ID to prevent different versions of the same package in one invocation
        set(MASON_PACKAGE_${_PACKAGE}_INVOCATION "${MASON_INVOCATION}" CACHE INTERNAL "${_PACKAGE} invocation ID" FORCE)
    endif()
endfunction()

macro(target_add_mason_package _TARGET _VISIBILITY _PACKAGE)
    if (NOT MASON_PACKAGE_${_PACKAGE}_INVOCATION)
        message(FATAL_ERROR "[Mason] Package ${_PACKAGE} has not been initialized yet")
    endif()

    target_include_directories(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_INCLUDE_DIRS}")
    target_compile_definitions(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_DEFINITIONS}")
    target_compile_options(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_OPTIONS}")
    target_link_libraries(${_TARGET} ${_VISIBILITY} "${MASON_PACKAGE_${_PACKAGE}_LIBRARIES}")
endmacro()

# Setup

string(RANDOM LENGTH 16 MASON_INVOCATION)

# Read environment variables if CMake is run in command mode
if (CMAKE_ARGC)
    set(MASON_PLATFORM "$ENV{MASON_PLATFORM}")
    set(MASON_PLATFORM_VERSION "$ENV{MASON_PLATFORM_VERSION}")
    set(MASON_PACKAGE_DIR "$ENV{MASON_PACKAGE_DIR}")
    set(MASON_REPOSITORY "$ENV{MASON_REPOSITORY}")
endif()

# Directory where Mason packages are located; typically ends with mason_packages
if (NOT MASON_PACKAGE_DIR)
    set(MASON_PACKAGE_DIR "${CMAKE_SOURCE_DIR}/mason_packages")
endif()

# URL prefix of where packages are located.
if (NOT MASON_REPOSITORY)
    set(MASON_REPOSITORY "https://mason-binaries.s3.amazonaws.com")
endif()

mason_detect_platform()

# Execute commands if CMake is run in command mode
if (CMAKE_ARGC)
    # Collect remaining arguments for passing to mason_use
    set(_MASON_ARGS)
    foreach(I RANGE 4 ${CMAKE_ARGC})
        list(APPEND _MASON_ARGS "${CMAKE_ARGV${I}}")
    endforeach()

    # Install the package
    mason_use(${_MASON_ARGS})

    # Optionally print variables
    if(DEFINED MASON_PACKAGE_${CMAKE_ARGV4}_${CMAKE_ARGV3})
        # CMake can't write to stdout with message()
        execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${MASON_PACKAGE_${CMAKE_ARGV4}_${CMAKE_ARGV3}}")
    endif()
endif()